Commit Graph
2 Commits
Author SHA1 Message Date
Aamer Akhter d1bfbb4fcf fix(cases): tell an unreachable path from an absent one, scope the stall cap
The bounded path probe answered "absent" both when a path did not exist and
when it simply did not answer, so a stalled linked case 404'd and the Run
button scaffolded a stray local case over it, and two stalled paths anywhere
made every unrelated path read as absent (hooks skipped, statusLine
overridden, the clone warning lost).

- probePath()/probePathKind() are tri-state: present (or directory/file),
  absent (ENOENT/ENOTDIR only) and unknown (timeout, other errors, refusal).
  boundedPathExists() stays as the display-only boolean.
- A stalled path takes only its own mount out of probing (deepest mount
  point from /proc/self/mounts, never /; just the path itself when there is
  no mount table). Unrelated paths keep probing. The process-wide cap is a
  backstop that answers unknown, and a single-path user request can probe
  past it ({ pastCap: true }), still bounded and still recorded as stalled.
  One console.warn when a path first stalls and one when the cap engages.
- GET /api/cases/:name keeps NOT_FOUND for definite absence only. An
  unreachable linked case answers with its registered path and
  unreachable: true; a local one answers OPERATION_FAILED. runClaude and
  runShell create a case only on errorCode NOT_FOUND. The case list keeps an
  unreachable linked case, marked unreachable, instead of dropping it, and
  fix-plan reports an unreadable plan as an error, not "no plan".
- applyWorkspaceHooks and the statusLine helpers skip only a workspace that
  is absent or on the stalled mount; a capacity refusal no longer stops
  hooks being installed elsewhere, and an unreadable settings file never
  lets the exporter override a user's own statusLine.
- The clone flow's repo-settings warning is back on its synchronous check,
  and stripCaseEnvKeys uses pathExistsForWrite.
- POST /api/sessions (workingDir) and POST /api/quick-start (case folder)
  probe with the bounded probe instead of statSync/existsSync. Missing and
  non-directory keep INVALID_INPUT; unknown is OPERATION_FAILED, and
  quick-start never scaffolds over a folder that did not answer.
- PATH_PROBE_TIMEOUT_MS and MAX_STALLED_PATH_PROBES move to
  src/config/path-probe.ts, overridable via CODEMAN_PATH_PROBE_TIMEOUT_MS
  (default 1500) and CODEMAN_PATH_PROBE_MAX_STALLED (default 3), and are
  documented in the Settings Reference.
- The probe is exported from the utils barrel and imported from there.
2026-10-04 20:30:40 -04:00
Saqeb Akhter 00b935abe6 fix(cases): bound linked-workspace path probes so an unreachable mount cannot freeze the server
A linked case can live on a network mount. When that mount goes away, a
hard mount makes stat() wait indefinitely, and the existsSync() probes in
the case routes and the workspace hook/statusline helpers ran on the event
loop, so a single GET /api/cases (or a session create in that workspace)
froze the whole web server until the mount came back.

Add boundedPathExists() (src/utils/bounded-path-probe.ts): an async stat
that answers "absent" after 1.5 s, shares one in-flight probe per path,
remembers a timed-out path until its stat finally settles, and refuses to
start new probes while two stalled ones still hold libuv threadpool
workers. Route the read-side probes in case-routes.ts and hooks-config.ts
through it. The settings writers in hooks-config.ts use an async lstat
that treats only ENOENT as missing, so an unreachable workspace is never
mistaken for an empty one and has its settings recreated.
2026-10-04 20:08:05 -04:00