Skip to content

fix(cases): bound linked-workspace path probes so an unreachable mount cannot freeze the server - #516

Open
aakhter wants to merge 1 commit into
Ark0N:masterfrom
aakhter:pr/bounded-path-probe
Open

aakhter wants to merge 1 commit into
Ark0N:masterfrom
aakhter:pr/bounded-path-probe

Conversation

@aakhter

@aakhter aakhter commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

If a linked case lives on a network mount (NFS, SMB, sshfs) that becomes unreachable, the whole web UI freezes. The case list and session creation ran synchronous existsSync checks on that path, and on a hard mount those block the Node event loop until the mount comes back.

This adds a small boundedPathExists helper (src/utils/bounded-path-probe.ts). It stats asynchronously and reports the path as absent after 1.5 s. It remembers a stalled path until its stat finally returns, so callers don't pile up probes on it, and it stops starting new probes while two stalled ones still hold libuv threadpool workers.

The read-side probes in the case routes and in the workspace hook/statusline helpers now use it. The settings writers use an async lstat that only treats ENOENT as missing, so an unreachable workspace is never mistaken for an empty one and overwritten.

Behaviour during an outage: an unreachable linked case is left out of GET /api/cases, and GET /api/cases/:name returns NOT_FOUND until the mount answers again. Writes to that workspace still wait on the mount, but they no longer block the event loop.

Tests

  • Unit tests for the helper: timeout, a single shared probe per path, the stalled-probe cap, recovery, and no false negatives for concurrent healthy paths.
  • A route test where a linked case's stat never settles but GET /api/cases still answers promptly. It fails on master (about 4 s).
  • typecheck, lint, format and check:browser-excludes pass, and the 15 test files that cover hooks-config and case-routes pass (402 tests).

I imported the helper directly rather than through the ./utils barrel. Happy to move it into the barrel if you prefer.

…t 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.
@Ark0N

Ark0N commented Oct 1, 2026

Copy link
Copy Markdown
Owner

DRAFT_COMMENT

@aakhter

aakhter commented Oct 3, 2026

Copy link
Copy Markdown
Contributor Author

Hi @Ark0N, it looks like your comment here came through as the placeholder text "DRAFT_COMMENT", so I think the actual review didn't post. Happy to pick it up whenever you get a chance to re-send it.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants