Skip to content

feat(worktree): release a Claude session's checkout when the session ends - #556

Merged
kixelated merged 5 commits into
mainfrom
quest/m0/session-checkouts
Nov 7, 2026
Merged

kixelated merged 5 commits into
mainfrom
quest/m0/session-checkouts

Conversation

@kixelated

@kixelated kixelated commented Nov 7, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Before this change, a checkout quest worktree hook made for a Claude Code session (--worktree, EnterWorktree, --bg, agent view, remote control) was never released, so session checkouts piled up with their build output. Now:

  • quest worktree hook also handles SessionStart and SessionEnd, and the README's opt-in JSON gains both entries. SessionEnd asks for "timeout": 60, the most Claude Code allows.
  • A checkout made for a session, meaning any WorktreeCreate name other than a subagent's agent-* or a workflow agent's wf_*, records the sessions that created it or started in it, by ID, and whether each is still running (sessions on the registry record).
  • SessionEnd marks the session ended. Once no recorded session runs, it releases the checkout as a subagent's release does, deleting declared scratch but keeping .tmp/.
  • SessionStart claims the checkout again, either for a session that held it before or for any session starting inside it. When a release had deleted scratch, it tells the session what to reinstall.
  • /clear ends a session only to start another in the same checkout, so it hands the checkout over without releasing it.
  • The release runs in two steps. The first records a failure ("release was cut short ...") before pruning, so a release that gets killed leaves the checkout reserved and visible in quest worktree status until a session starts there or quest worktree release finishes it.
  • Checkouts the hook did not make for a session (subagents', quest worktree create, quest agent) are left alone.

Choices made without asking

  • The entries stay in the README's pasted JSON rather than folding into quest setup. That is what the quest's Plan says. Folding the worktree hooks into quest setup would replace the pasted block, and moq-dev/quest#544's guard on it, with one command. That is suggested as a follow-up below.
  • /clear does not release. The Plan accepted a release followed by a reclaim on /clear. That would delete node_modules and other build output on every /clear for nothing, so a clear reason only hands the checkout over.
  • Ended sessions stay on the record. The live probe showed that claude --resume fires SessionStart with the launch directory as cwd, before Claude enters the worktree. A lookup by cwd alone would never reclaim the checkout, so the hook also matches on session ID. The record grows by one entry per session that used the checkout and goes away when the checkout is removed.
  • .tmp/ survives a session's release, as it does a subagent's, so a resumed session still has its reports and logs.
  • WorktreeCreate now requires session_id (a common field Claude Code always sends) for a session's checkout, and fails loud without it.

Public API and wire impact

  • quest worktree hook handles two more events, SessionStart and SessionEnd; its help text says so.
  • Registry records gain an optional sessions map. Per AGENTS.md, nothing is migrated.
  • README hook JSON: two new entries. Users who opted in need to add them.

Validation

  • quest gate just check: passes.
  • quest gate just test on the merged head: passes (1171 passed, 5 skipped; worker 539 passed). One earlier run under host load failed test/gate.test.ts › concurrent processes never admit past the headroom, and admit every caller, a test this PR doesn't touch; the rerun passed.
  • New unit test holds a session's checkout while a session uses it and releases it when the last ends covers: two sessions sharing a checkout; ending a session that holds nothing; /clear handover; release from outside the checkout; resume from the launch directory with the reinstall note; a cut-short release finished by the next session; and the subagent and manual checkouts it must leave alone.

Live verification (Claude Code 2.1.319)

Run from a scratch repository under this checkout's .tmp/, not itself worktree-isolated. Hooks came from --settings, through a wrapper that logged each input and pointed QUEST_WORKTREE_HOME at a scratch registry, so the real registry was never touched.

Route Events seen Result
claude -p --worktree WorktreeCreate, SessionStart (cwd = worktree), SessionEnd other, all with one session ID released, a 50 MB cache/ deleted
claude -p --resume <id> SessionStart resume with cwd = launch directory, then SessionEnd in the worktree reclaimed by session ID; Claude quoted the reinstall note; released again at the end
EnterWorktree from a -p session SessionStart, WorktreeCreate (same ID), SessionEnd in the worktree released
Interactive --worktree + /clear + exit SessionEnd clear, SessionStart clear with a new ID in the worktree, SessionEnd handed over without release, then released
claude --bg --worktree + claude stop WorktreeCreate, SessionStart, SessionEnd other on stop released
Remote control not run the maintainer dropped this live check on 2026-11-04; release covers bridge-* if SessionEnd fires there

Timing: releasing about 196k entries (12 hard-linked copies of this repository's node_modules) took 0.85 s through the hook, against Claude's 1.5 s default SessionEnd budget. A cold cache or a larger Rust target/ would overrun that default, hence the 60 s timeout.

Killed release: a SessionEnd with about 489k entries was killed (SIGKILL) at 0.6 s. The record stayed active with the cut-short failure shown in quest worktree status. The registry lock was reclaimed. A SessionStart resume from the launch directory then reclaimed the checkout, cleared the failure, and printed the note.

Probe side effects: the scratch repository's path under .tmp/ was marked trusted in Claude's config, done by Claude's own trust prompt, so --bg would run. The probe's background session was removed with claude rm.

Quest

Completes quest/m0/session-checkouts.md: the file is deleted and its link removed from quest/m0/README.md.

Caused by

None. Session checkouts have never been released since the hooks were added, so this gap is as old as the hooks rather than a regression from one PR.

Suggested follow-ups

  • Fold the worktree hooks into quest setup. Recommendation: plan it. quest setup (from moq-dev/quest#517) already merges quest hook entries into the user's settings. An opt-in flag such as quest setup --worktrees, with quest hook dispatching the six worktree events, would replace the pasted README JSON. It would also replace fix(hooks): name the launch route when quest is missing from a hook's PATH #544's PATH guard repeated in every entry, and users would pick up new events, like these two, by rerunning one command.

Models

Size: M
Implementer: Claude Code, claude-opus-5-5, medium effort

(Written by Claude Opus 5.5 with medium effort)

Review

Rewritten by quest review after each round.

  • Reviewer: Codex, gpt-6.1-sol, medium effort
  • Session: 01a1b815-77e1-73e0-88e1-fb419ff30e52, round 3
  • Head: 1efab2f
  • Base: origin/main at 2108b1d
  • Score: 10/10, The incremental changes merge main and adapt the hook documentation and coverage consistently, with no new findings in the branch’s changes.
  • Findings: none

@pullfrog

pullfrog Bot commented Nov 7, 2026

Copy link
Copy Markdown

Pullfrog  | Review this ➔

kixelated and others added 2 commits November 7, 2026 13:55
…ends

quest worktree hook now handles SessionStart and SessionEnd. A checkout it
makes for a session (not a subagent) records each session that created or
started in it; the last one to end releases it, keeping .tmp/, and a session
that resumes or starts there claims it again. /clear hands it to the next
session without a release. A release cut short stays recorded as a failure.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated kixelated changed the title A Claude session's checkout is released when the session ends feat(worktree): release a Claude session's checkout when the session ends Nov 7, 2026
…submodule sessions

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated
kixelated marked this pull request as ready for review November 7, 2026 22:20
@coderabbitai

coderabbitai Bot commented Nov 7, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 23 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 6d381425-605f-400e-b029-085ab77af4ef
Commits

Reviewing files that changed from the base of the PR and between 2108b1d and 1efab2f.

Files selected for processing: 7

  • README.md
  • quest/m0/README.md
  • quest/m0/session-checkouts.md
  • src/cli/main.ts
  • src/cli/worktree-hook.ts
  • src/cli/worktree.ts
  • test/worktree.test.ts
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@kixelated kixelated left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review by review (OpenAI)

Reviewed head 6ef7b315ba70fed014de82b628ef7b4f4676bbc0 against base d742cea566ccac03d7822060602c48f1ba064867.

Finding: [P2] Preserve the session workspace when a supervised run finishes last. In src/cli/worktree.ts:947 (also reconcile() at line 505), the deferred release calls settle() without the session hook's keep list. For a session checkout with declared scratch ["cache", ".tmp"], start a supervised run, then end the last Claude session before that run exits. detach(session, true, [".tmp"]) marks the session false but returns at line 892 because the run is still present. When that run finishes, this call prunes both directories, deleting the reports/logs that the new session-release contract says survive for a resumed session. Orphan reconciliation has the same outcome.

Carry the session-release retention policy through deferred run completion and reconciliation. Add coverage for this reverse ordering with an actual .tmp file; the new supervised-run test only finishes the run while the session is still active, then ends the session.

The overall direction is sound: session-ID tracking handles resume from the launch directory, and the two-stage immediate release makes interrupted cleanup visible. The prior fixes described in the PR body are present, but the mixed session/run lifecycle still needs the retention fix above.

Verification: static review through the GitHub connector of all seven changed files and the surrounding lifecycle code, plus comparison with the base. No tests, builds, shell commands, or live Claude sessions were run. The PR was open and non-draft with unchanged head/base and no submitted reviews immediately before posting. This is a COMMENT review, not an approval.

(Written by review (OpenAI))

@kixelated
kixelated added this pull request to the merge queue Nov 7, 2026
Merged via the queue into main with commit 5f29613 Nov 7, 2026
14 checks passed
@kixelated
kixelated deleted the quest/m0/session-checkouts branch November 7, 2026 23:15
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