Skip to content

A restore whose working directory is gone says so, and keeps the agent's session - #72

Open
0xR32 wants to merge 2 commits into
autonomous-ai:mainfrom
0xR32:fix/restore-missing-workdir-cli
Open

0xR32 wants to merge 2 commits into
autonomous-ai:mainfrom
0xR32:fix/restore-missing-workdir-cli

Conversation

@0xR32

@0xR32 0xR32 commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

What does this help someone do?

Keeps an agent whose working directory was deleted while it was not running — a removed git
worktree is the common one — from becoming a dead pane nobody can explain or recover.

A Mac rebooted. The daemon restored eight agents; seven came back. The eighth left this:

zsh:cd:1: no such file or directory: …/agentmon-teams/.claude/worktrees/feat-org-links
harness: the selected working directory is unavailable.
Pane is dead (status 1, Thu Sep 17 06:19:49 2026)

The day before, a different agent in the same repository had been asked to clean up merged
branches and ran git worktree remove .claude/worktrees/feat-org-links. Nothing connected the two.

What the restore did with that, from harness.log:

06:19:43.149 [restore] claude · agent 842b2c3f · pane %6 → %2 · resuming 0c64cfdd
06:19:47.723 [restore] … did not come back up resuming its session — retrying fresh
06:19:53.034 [restore] … failed · claude exited before its engine process became ready.

Four things went wrong, and only the first is the missing folder:

  1. A pane was opened for a path that cannot work. restoreAgents never checked cwd. The
    shell's cd guard failed, it exited 1, and remain-on-exit kept the corpse.
  2. The fallback retried the same impossible path. relaunchFresh only drops --resume.
  3. The doomed retry cost the conversation. relaunchFresh calls unbindSession before it
    builds, so the row's sessionId became "" and transcriptPath null — for a retry that never
    had a chance. The transcript survived on disk; the agent no longer pointed at it.
  4. The row can never come back. Marked launch: failed, and every later restore skips it.

The diagnosis that reached the user was ENGINE_DID_NOT_START — "claude exited before its engine
process became ready. See the terminal output for details." The real cause was one line above, in a
pane, on a machine the reader may not be sitting at.

agent_create already gets this right: it refuses a missing folder with CWD_NOT_FOUND before
opening any pane, and the app already has copy for that code. This applies the same contract to the
one path that skipped it.

The change, in three parts:

  • One shared availability test (workdirAvailable.ts) so create and restore cannot drift on what
    "available" means. agent_create's inline statSync now calls it, and gains a detail.
  • Restore refuses at build time. buildLaunch already has an error channel restoreAgents
    handles, so the row is marked CWD_NOT_FOUND with a sentence naming the folder, no pane is
    opened
    , and unbindSession is never reached.
  • Two ordering fixes in restoreAgents.ts. relaunchFresh builds before it unbinds, so a launch
    that cannot be built stops costing the agent its session (this also covers a folder deleted while
    the agent runs). And the pre-flight skip gains exactly one exception: a row that failed with
    CWD_NOT_FOUND whose folder is back is retried instead of skipped — the one recorded failure whose
    precondition is observable from outside the agent. Put the worktree back, restart the daemon, and
    the agent returns on its own.

No fallback directory, deliberately: an agent that silently starts in the parent of its deleted
worktree is an agent committing to the wrong branch.

How to try it

cd /tmp && rm -rf repoint-demo && mkdir repoint-demo && cd repoint-demo
git init -q . && git commit -q --allow-empty -m init
git worktree add -q .claude/worktrees/feat-demo -b feat-demo

Create an agent in /tmp/repoint-demo/.claude/worktrees/feat-demo, let it start and bind a session,
then stop the daemon, end the agent's process, delete the worktree, and start the daemon again.

Before:

[restore] claude · agent … · pane %9 → %10 · resuming a697b7d3
[restore] … did not come back up resuming its session — retrying fresh
[restore] … failed · claude exited before its engine process became ready.

→ a dead pane, sessionId: "", transcriptPath: null.

After:

[restore] claude · agent … · could not build its launch · its folder
  /tmp/repoint-demo/.claude/worktrees/feat-demo no longer exists — put it back, or create the
  agent again somewhere else
[restore] restored 0 · skipped 0 · failed 1

→ no pane opened, sessionId and transcriptPath intact, and the app's "Start failed" notice shows
that sentence (it already renders launchDetail verbatim, so no app change is needed).

Then git worktree add .claude/worktrees/feat-demo feat-demo and restart the daemon: the agent comes
back by itself — restored 1 · engine up.

What did you verify?

macOS 15 (Darwin 25.6.0), Node 22, tmux 3.7c, engine claude 2.1.274.

  • cd cli && npm run typecheck — clean
  • cd cli && npm test2,896 passed, 51 skipped, 0 failed (rebased onto main at e6a433e)
  • cd cli && npm run test:tmux-real7 passed, 9 engine rows unavailable (not installed here:
    kilo, grok, copilot and others; an unavailable row is not a passing one)
  • Live, on a real daemon against the real ~/.harness folders, both against a purpose-built
    agent and against the actual agent from the incident above. Same registry, same input, two
    daemons two minutes apart: the installed build produced ENGINE_DID_NOT_START and destroyed the
    session binding; this build produced CWD_NOT_FOUND, opened no pane, and kept sessionId and
    transcriptPath. Recreating the worktree brought the agent back on the next start
    (engine up · 12405ms) with no other action.

New tests: workdirAvailable.spec.ts (6) and four in restoreAgents.spec.ts — a missing-cwd row is
marked CWD_NOT_FOUND and createPane is never called and its sessionId survives; the fresh
relaunch keeps the binding when the rebuild fails; the self-heal fires for CWD_NOT_FOUND only. One
assertion was added to an existing fallback test to pin that the binding is still discarded on the
path that fallback was written for — a resume id the engine refuses.

Known limitations

  • A row already marked ENGINE_DID_NOT_START by an older build stays skipped; only CWD_NOT_FOUND
    self-heals. Rows that failed before this lands need the agent recreating.
  • The message says what to do without offering a control to do it. A follow-up adds agent_repoint
    (point a stopped agent at another folder from the app); it is held back deliberately, because it
    puts a new type in ENCRYPTED_DOWN_TYPES and so re-pins the e2ee/core.ts interop keystone that
    the browser client and the paired device share. That coordination should not ride along with a bug
    fix. Nothing in this PR touches core.ts.

🤖 Generated with Claude Code

0xR32 and others added 2 commits September 17, 2026 10:00
…h agent_create

agent_create has refused a missing folder since it was written, before opening any pane. Restore
never made the same check. Sharing the test is what keeps the two from drifting on what available
means, and gives the refusal a sentence worth rendering.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three changes to one path:

  * the launch is refused at build time, so no pane is opened for a folder that cannot work, and
    the failure names the folder instead of blaming the engine;
  * the fresh retry builds BEFORE it unbinds, so a launch that cannot be built stops costing the
    agent the conversation it was resuming;
  * a row that failed only because its folder was missing is retried once the folder is back --
    the one recorded failure whose precondition is observable from outside the agent.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@0xR32
0xR32 force-pushed the fix/restore-missing-workdir-cli branch from 778a16f to 423e892 Compare September 17, 2026 08:02
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.

1 participant