Skip to content

instructions: RangeError stack overflow activating AGENTS.md for case-variant Windows location #50750

Closed

Description

@quirontech

Summary

On Windows, a location whose directory casing differs from its resolved project.directory (same project id, case-insensitive filesystem) makes instruction-source activation recurse infinitely: ancestorDirectories() never matches its stop argument (case-sensitive ===) and dirname("C:\") is a fixed point, so the walk overflows the stack (RangeError: Maximum call stack size exceeded) on every activation/reload. The failure handler then marks instructions unavailable, so that location silently receives no AGENTS.md instructions at all.

Environment

  • opencode version: v2.0.14
  • OS: Windows 10.0.22631.6199 (win32 x64)
  • Terminal: TERM=xterm-256color (TERM_PROGRAM, COLORTERM unset)
  • Shell: C:\WINDOWS\system32\cmd.exe (%ComSpec%); repro commands run in PowerShell 5.1
  • Install/channel: global npm install of @opencode/cli, update channel latest
  • Active plugins: ensure-claude-mem, gemini-env (global), claude-mem-opencode, graphify (project) — all active, all auto-discovered (no plugins key in any config). The crash happens in the built-in opencode.config.instruction source, unrelated to plugins.

Reproduction

  1. On Windows (case-insensitive FS), have a project whose on-disk directory is C:\dev\krankly, and register a location that invoked the same directory with different casing (e.g., a client started from a historical cwd C:\dev\Krankly). Both locations share the same project id.
  2. Confirm the mismatch:
    • GET /api/debug/location → [{"directory":"C:\\dev\\krankly"},{"directory":"C:\\dev\\Krankly"},{"directory":"C:\\Users\\<user>"}]
    • GET /api/location?location[directory]=C:\dev\Krankly → project.directory = "C:\\dev\\krankly" (case differs from directory)
  3. Trigger instruction activation for the mismatched location: opencode api post /api/location/reload --param "location[directory]=C:\dev\Krankly" (it also fires on plugin/config reloads and watcher-driven rescans).
  4. Read the server log.

Expected Behavior

The ancestor walk should treat start === stop the same way the branch gate does (case-insensitively on Windows, consistent with FSUtil.contains/path.relative), stop at the project root (or at the filesystem root as a safety net), and load that location's AGENTS.md instructions.

Actual Behavior

Every activation logs (4 occurrences in 3 minutes, one per reload cycle):

timestamp=2026-09-22T21:20:09.132Z level=WARN message="failed to activate instruction source" cause="Cause([Die(RangeError: Maximum call stack size exceeded.)])"
timestamp=2026-09-22T21:22:01.753Z level=WARN message="failed to activate instruction source" cause="Cause([Die(RangeError: Maximum call stack size exceeded.)])"
timestamp=2026-09-22T21:22:08.009Z level=WARN message="failed to activate instruction source" cause="Cause([Die(RangeError: Maximum call stack size exceeded.)])"
timestamp=2026-09-22T21:23:32.407Z level=WARN message="failed to activate instruction source" cause="Cause([Die(RangeError: Maximum call stack size exceeded.)])"

The catch then runs discovery.transform((editor) => editor.unavailable()): sessions under the C:\dev\Krankly location get no project AGENTS.md (verified — project rules were absent from that location's context while the identically-cased C:\dev\krankly location loaded them fine). The sibling warning failed to load instruction source never appears, confirming the crash happens before any source read (in the candidate-subscription walk, not in fs.up).

Additional Context

Root cause in the shipped file packages/core/src/config/plugin/instruction.ts (present at tag v2.0.14):

const project = discovery.project && FSUtil.contains(root, start)   // case-insensitive gate → true
const stop = FSUtil.contains(home, start) ? home : root             // root = "C:\dev\krankly"
// start = "C:\dev\Krankly"
const candidates = [...(project ? ancestorDirectories(start, stop) ... : [])]

function ancestorDirectories(start: string, stop: string): string[] {
  if (start === stop) return [start]                                 // case-sensitive → never true
  return [start, ...ancestorDirectories(dirname(start), stop)]       // dirname("C:\") === "C:\" → infinite recursion
}
  • FSUtil.contains uses relative() (case-insensitive on win32), so the walk IS entered; start === stop compares raw strings and never matches C:\dev\Krankly vs C:\dev\krankly; walking up reaches C:\, where dirname("C:\") === "C:\" is a fixed point with no base case → stack overflow.
  • Suggested fix: compare start/stop with the same case-folding used by contains (win32 case-insensitive), and/or add the parent === current base case that FSUtil.up/findUp/globUp already have:
function ancestorDirectories(start: string, stop: string): string[] {
  const eq = (a: string, b: string) => (process.platform === "win32" ? a.toLowerCase() === b.toLowerCase() : a === b)
  const out: string[] = []
  for (;;) {
    out.push(start)
    if (eq(start, stop)) return out
    const parent = dirname(start)
    if (parent === start) return out // filesystem root reached — stop instead of recursing
    start = parent
  }
}
  • Note on current dev: packages/core/src/config/plugin/instruction.ts no longer exists (logic moved to packages/core/src/instruction-context.ts), which uses the iterative, root-guarded fs.up(). Two follow-ups: (1) confirm which releases up to v2.0.14 are affected and ship a fix/patch; (2) fs.up's options.stop === current comparison is still case-sensitive, so a case-differing start/stop walks all the way to the filesystem root instead of stopping at the project root (no crash, but it can pick up AGENTS.md files above the project).
  • Frequency: consistent — fires on every activation/reload for the affected location.
  • Workarounds: keep every client's cwd casing identical to the on-disk casing (prevents the mismatched location from being re-registered) and restart the service to drop in-memory locations. OPENCODE_DISABLE_PROJECT_CONFIG=1 skips the walk but disables project instructions everywhere.
  • Recent changes: observed immediately after upgrading from v1.8.x to v2.0.14 (forced upgrade).

Activity

  1. github-actions commented on Sep 22, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions