From 6118698d8cb6bd82e2242918b5f384ffe3574267 Mon Sep 17 00:00:00 2001 From: mika kuns Date: Thu, 6 Aug 2026 13:28:04 +0200 Subject: [PATCH] docs(explore-notes): document interactive session id persistence and resume precedence --- docs/explore-notes/conpty-sessions.md | 32 +++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/docs/explore-notes/conpty-sessions.md b/docs/explore-notes/conpty-sessions.md index a6370d06..d6eecded 100644 --- a/docs/explore-notes/conpty-sessions.md +++ b/docs/explore-notes/conpty-sessions.md @@ -1,7 +1,7 @@ # ConPTY interactive sessions & launch specs > **Explore-note — verify before trusting.** Distilled map of a subsystem, not authoritative. -> Last verified against commit `aac84e4` (2026-08-06). +> Last verified against commit `1a988ff` (2026-08-06). > Drift check: `git log --oneline bdee731..HEAD -- src/ClaudeDo.Worker/Planning src/ClaudeDo.Worker/Hub src/ClaudeDo.Worker/Runner/ClaudeArgsBuilder.cs src/ClaudeDo.Ui/ViewModels/MissionControlViewModel.cs src/ClaudeDo.Ui/Views/InteractiveTerminalView.axaml` > Stable structure only (no line numbers). See docs/explore-notes/README.md. @@ -35,7 +35,7 @@ Two independent reasons: A fresh task session's brief lives at `~/.todo-app/task-sessions//brief.md` (`InteractiveLaunchSpecService.BuildFreshTaskArgsAsync`). A task with neither title nor -description skips the file **and** the positional arg entirely. +description skips the file **and** the positional arg entirely — it still gets `--session-id`. ## Argument ordering @@ -46,6 +46,34 @@ except for a fresh task session with a brief, where `--add-dir ` mus `--model` is deliberately **NOT** forced on an interactive session — the user can still switch models in the TUI. +## Resuming a task session (`TaskEntity.InteractiveSessionId`) + +`claude --session-id ` lets the caller pre-assign a conversation's session id instead of +waiting for the CLI to generate one. `BuildForTaskAsync` uses this so a closed or aborted +interactive task session can be resumed even if it never got far enough to write anything to its +own transcript: + +1. **Resume check.** If the task isn't on a freshly (re)created worktree, `BuildForTaskAsync` + picks a session to resume with `task.InteractiveSessionId ?? run?.SessionId` — this task's own + last *interactive* conversation takes precedence over the latest *autonomous* run's session, + since they're distinct conversations even against the same worktree. A task that has only ever + run autonomously still resumes into that run's session the first time it's opened interactively + (this is the pre-existing behavior `run?.SessionId` alone used to provide). +2. **Fresh path.** If neither is available (never run any way, or `isFreshWorktree`), a new + `Guid.NewGuid()` is generated and persisted to `TaskEntity.InteractiveSessionId` via + `TaskRepository.SetInteractiveSessionIdAsync` — **before** the `LaunchSpec` is returned, i.e. + before the ConPTY host ever spawns `claude`. `BuildFreshTaskArgsAsync` then passes it as + `--session-id `, placed as the single-value flag directly before the positional kickoff + (or, with no brief, right after `--effort`). +3. **Fresh worktree wins.** `isFreshWorktree` forces `run` to `null` *and* is checked before + reading `task.InteractiveSessionId`, so a recreated worktree never resumes a stale id from + either source — it always takes the fresh path, which overwrites the stale + `InteractiveSessionId` with the new one. + +Net effect: reopening an interactive session for a task (pane closed, process killed, whatever) +resumes the same claude conversation, because the id was committed to the DB before the previous +launch even started. + ## List handler ("Let Claude handle it") `BuildForMergeHelperAsync` uses `--permission-mode auto` so it runs unattended. The