diff --git a/plugins/session-flow/.claude-plugin/plugin.json b/plugins/session-flow/.claude-plugin/plugin.json index 7307b4396..0a438ed0a 100644 --- a/plugins/session-flow/.claude-plugin/plugin.json +++ b/plugins/session-flow/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "session-flow", - "version": "0.22.0", + "version": "0.22.1", "description": "Session-lifecycle toolkit of thirteen skills: workflow (navigate a staged dev workflow and suggest the next stage), handoff (write a save-point and resume prompt for /clear-and-resume), continue-in-background (delegate the task to a fresh background agent that continues it now — same save-point engine as handoff, delivered by launching a detached claude --bg session seeded with the resume prompt; launches only on explicit user request), keep-going (recover and continue after any interruption OR when live off-thread work looks stalled — inventory off-thread work, inspect its real output, act only on evidence, then continue; after a usage limit lifts it continues rather than summarizing-and-stalling), find-handoff (recover a lost handoff after /clear — when the resume prompt was written but never copied — via a read-only detection ladder: known-location glob of the handoffs dir, then a bounded, recency-ranked transcript scan for the handoff directive and dashed-rail markers, then a confirm-before-resume gate; surfaces only the resume prompt + metadata, never raw transcript content), clean-stop (get to a durable, linked stopping point before the machine may go away — sweep every repo/worktree for uncommitted, unpushed, or PR-less work, push it durable, put breadcrumbs in PR/issue bodies, then give a free-and-clear verdict), retro (structured end-of-session retrospective with transcript metrics and learning codification), running-retro (in-flight retrospective checkpoints that spawn a subagent to analyze the transcript so far and append classified findings to a cumulative running ledger — capture and route only, the live counterpart to retro; also owns a detached-observer substrate that can watch a session out-of-band and run the checkpoint autonomously after the session ends), orient (read-only session orientation — synthesize where we stand, what we are doing, and why, from durable + off-thread state the built-in /recap never sees: ledgers, handoffs, workflow checklists, running-retro ledgers, open PRs and work-items, and git), orchestrate (arm a session or worker with proactive-orchestration imperatives), reanchor (verify a session's working assumptions are still true against live reality — referenced PRs/issues/branches, base-branch drift, renamed/version-drifted surfaces, stale memory-tier files, and the goal a handoff records, compared across the chain so a re-derived goal reports as drift — before building on them), reconcile (retire finished off-thread work and reconcile this session's task ledger with reality — the prune-and-reconcile counterpart to keep-going's resume: inventory the work this session spawned, inspect its real state, retire the finished and close proven-done tasks, auto-settling the finished and gating any kill of still-running work; sibling sessions in the project are reported read-only), and setup (check-centric verification of the observer's runtime prerequisites and configuration).", "author": { "name": "Melodic Software", diff --git a/plugins/session-flow/CHANGELOG.md b/plugins/session-flow/CHANGELOG.md index fd59110c7..0fe0b8589 100644 --- a/plugins/session-flow/CHANGELOG.md +++ b/plugins/session-flow/CHANGELOG.md @@ -1,5 +1,31 @@ # Changelog — session-flow plugin +## [0.22.1] + +### Fixed + +- **`find-handoff`: a background continuation that COMPLETED is no longer read as one that failed.** + The background-delivery screening rechecked the continuation's current state before excluding a + save-point, but keyed that recheck on `claude agents` presence and collapsed every absence into + "keep the candidate, noting the failed background attempt". `claude agents --json` lists ACTIVE + sessions only — a completed background session is excluded by the CLI and surfaces only under + `--all`, carrying a `state` (observed: `done`, `stopped`) where a live one carries a `status` + (observed: `idle`, `busy`); verified this session against `claude agents --help` ("`--all` — With + --json: also include completed background sessions") and a live `--json --all` sample, and it is + the same contract `claude-ops`' `lane-launcher.sh` (`load_sessions`) already relies on. So a + finished continuation looked identical to a dead one: the ladder surfaced its save-point as a lost + handoff labelled a failed attempt, inviting the operator to redo completed work and letting a + recent completed continuation bury the older manual handoff they were actually looking for. The + recheck now reads `claude agents --json --all` and resolves four ways instead of two — live + (exclude, work running), terminal-and-completed (exclude, work FINISHED, point at that session's + output), terminal-and-not-completed (keep, the restart artifact the recheck exists for), and + absent even from `--all` (UNKNOWN, keep, never called a failure, since the `--all` history is + bounded) — keyed on the launched `sessionId` where the transcript recorded one, with the + `continue-` slug remaining an ambiguous key that routes to UNKNOWN. Stated once at the + step-1 screening site and governing every screening site, with the prompt-only site and the + Gotchas bullet aligned to it; `evals.json` case 8's stale expectation corrected and a case added + for the completed-continuation branch. (claude-code-plugins#1033 review thread.) + ## [0.22.0] ### Removed diff --git a/plugins/session-flow/skills/find-handoff/SKILL.md b/plugins/session-flow/skills/find-handoff/SKILL.md index 5c8fd6da0..965fac61d 100644 --- a/plugins/session-flow/skills/find-handoff/SKILL.md +++ b/plugins/session-flow/skills/find-handoff/SKILL.md @@ -124,14 +124,52 @@ one, since the producer emits a separate re-arm message per surviving loop, so " be invisible and verifies the agent actually appeared, so exclusion requires transcript evidence of that verification (the agent listed/confirmed); this definition governs every screening site in this skill. Matched launch references this candidate and verifiably - succeeded → **recheck the CURRENT `claude agents` state before excluding** — transcript - evidence proves only launch-time persistence, and an agent that has since exited or failed - leaves this save-point as the artifact needed to restart the work. Continuation still live → - the save-point is not the lost handoff: exclude it from the default winner, say so, and keep - looking for the older manual handoff. Continuation absent or failed now → keep the candidate, - noting the failed background attempt. Launch references a different file, failed, or is - unverified/ambiguous → keep the candidate (surfacing the provenance at the confirm gate when - ambiguous). The current-state recheck applies to every screening site, prompt-only included. **v1 scope: current repo only.** The cross-repo *filesystem* sweep (deriving + succeeded → **recheck the CURRENT agent state before excluding** — transcript evidence proves + only launch-time persistence, and a continuation that has since died leaves this save-point as + the artifact needed to restart the work. + + **Read that state with `claude agents --json --all`, never the bare `--json`.** The bare form + lists ACTIVE sessions only — a background session that has reached a terminal state is excluded + by the CLI and surfaces only under `--all`, where it carries a `state` (observed: `done`, which + reports completion, and `stopped`, which does not) in place of the active `status` (observed: + `idle`, `busy`). That is `claude agents --help` ("`--all` — With --json: also include completed + background sessions") and the same + verified contract this repo already relies on in `claude-ops`' + `skills/lanes/scripts/lane-launcher.sh` (`load_sessions`). **So absence from the bare list is + NOT evidence of failure: a continuation that finished the work successfully looks exactly like + one that died.** Resolve four ways, and never collapse them: + - **Live** — present with an active `status`. The work is in progress, so the save-point is not + the lost handoff: exclude it from the default winner, say so, point the operator at + `claude agents`, and keep looking for the older manual handoff. + - **Terminal and completed** — present under `--all` with a `state` reporting completion. + **Also exclude it from the default winner**, and say the continuation FINISHED rather than + failed. This branch is why the recheck cannot key on presence alone: presenting a completed + continuation's save-point as the lost handoff invites the operator to redo work already done, + and it can bury the older manual handoff they were actually looking for. Point them at that + session's output, not at a rerun. + - **Terminal and not completed** — present under `--all` with a `state` that does not report + completion (an interrupted or stopped one). **Keep** the candidate as the restart artifact and + name the state observed. This is the case the recheck exists for. + - **Absent even from `--all`** — **UNKNOWN, never "failed."** The `--all` history is bounded, so + a long-finished session can age out of it, and a lookup that cannot see a session cannot say + why. Keep the candidate, and report that the continuation's outcome could not be determined + instead of asserting a failed background attempt. + + Read the `state` value as reported and say which branch it took — the values above are observed, + not a closed set, so an unfamiliar one is reported rather than forced into a branch. **Key the + lookup on the launched session's `sessionId`/`id` — the `claude agents --json` field, NOT the + `session_id` this file uses elsewhere for transcript frontmatter — when the launch-verification + listing recorded one in the transcript.** With no recorded ID the recheck is UNKNOWN and the + candidate is kept, and that holds even when the `--name "continue-"` slug matches exactly + one entry: `--all`'s history is bounded, so the candidate's own session may have aged out while a + newer same-topic continuation remains, and a unique match is then a DIFFERENT session wearing the + same name. Uniqueness at snapshot time is not identity across time. The slug is the same + ambiguous key here as at launch time, and no match count makes it unambiguous. Launch references + a different file, failed at launch, or is unverified/ambiguous → keep the candidate (surfacing + the provenance at the confirm gate when ambiguous). **This four-way resolution governs every + screening site in this skill, prompt-only included.** + + **v1 scope: current repo only.** The cross-repo *filesystem* sweep (deriving other repo roots from transcript `cwd` fields) is deferred — step 2's transcript scan already recovers handoffs written in other repos, since transcripts are indexed by session, not repo. @@ -234,8 +272,12 @@ one, since the producer emits a separate re-arm message per surviving loop, so " a temp file (visible in the same transcript as the Write preceding the launch) — a launch excludes only the block whose content it delivered; a later launch of a *different* prompt never disqualifies an earlier manual block in the same transcript. A delivered block is not a - lost handoff (its work is already running — `claude agents` lists it); exclude it and keep - scanning. + lost handoff; exclude it and keep scanning. **Resolve the continuation's current state through + step 1's four-way rule before excluding** (`claude agents --json --all`, keyed on the + launched `sessionId` where the transcript recorded one): live and completed both exclude — + the first because the work is running, the second because it is already done — while a + non-completion terminal state, or absence even from `--all`, keeps the block. Absent from the + bare active list is never on its own a failed continuation. - **Capture the below-rail `/loop` re-arm entries — every mode, every discovery path, every loop.** Once a candidate qualifies on the signals above, also take the re-arm instructions the producer emits below the bottom rail. **Read the `Re-arm of lines:` headers and @@ -432,11 +474,16 @@ one, since the producer emits a separate re-arm message per surviving loop, so " the `continue-` slug alone (topic-only: same-topic files all match; ambiguous unless it uniquely resolves). "Successful" always means verified-visible — transcript evidence the agent appeared, never exit-0 alone, since the producer itself warns a zero-exit launch can be - invisible; unverified → ambiguous, keep the candidate. Exclusion also requires the continuation - to be live in the CURRENT `claude agents` state — historical visibility is launch-time only, - and a since-exited or failed agent leaves the save-point as the restart artifact. Exclude only - the correlated, verifiably successful, still-live launch's file and point the operator at - `claude agents` instead. + invisible; unverified → ambiguous, keep the candidate. Exclusion also turns on the + continuation's CURRENT state, because historical visibility is launch-time only — and that state + must be read with **`claude agents --json --all`**. The bare `--json` lists active sessions only, + so a continuation that COMPLETED SUCCESSFULLY is absent from it and indistinguishable there from + one that died; treating that absence as failure surfaces a finished continuation's save-point as + a lost handoff, which invites redoing done work and can bury the older manual handoff. Apply + step 1's four-way resolution: live → exclude (running); terminal and completed → exclude, saying + it FINISHED and pointing at that session's output; terminal and not completed → keep, as the + restart artifact; absent even from `--all` → UNKNOWN (the history is bounded), keep, and do not + call it a failure. Prompt-only continuations screen the same way, bound by content: a rails block whose exact prompt a verifiably successful `claude --bg` launch delivered (the producer writes it to a temp file in the same transcript first) was delivered, not lost — a later launch of a different diff --git a/plugins/session-flow/skills/find-handoff/evals/evals.json b/plugins/session-flow/skills/find-handoff/evals/evals.json index 7893ab882..69f1a1de5 100644 --- a/plugins/session-flow/skills/find-handoff/evals/evals.json +++ b/plugins/session-flow/skills/find-handoff/evals/evals.json @@ -97,16 +97,16 @@ "id": 8, "name": "excludes-background-launch-save-points", "prompt": "Find my lost handoff. Earlier today I also ran /session-flow:continue-in-background on a different task.", - "expected_output": "The skill recognizes that continue-in-background writes an indistinguishable type: handoff save-point with the same engine and then launches the continuation itself. It screens glob candidates by locating each file's producer transcript via session_id and checking for the background-launch signature (claude --bg --name \"continue-…\"). A save-point whose launch succeeded is excluded from the default winner — its work is already running (claude agents lists it) — and the skill keeps looking for the older manual handoff, surfacing provenance at the confirm gate when launch evidence is ambiguous.", + "expected_output": "The skill recognizes that continue-in-background writes an indistinguishable type: handoff save-point with the same engine and then launches the continuation itself. It screens glob candidates by locating each file's producer transcript via session_id and checking for the background-launch signature (claude --bg --name \"continue-…\"). Before excluding, it rechecks the continuation's CURRENT state with `claude agents --json --all` — never the bare --json, which lists active sessions only — and resolves four ways: live and completed both exclude the candidate from the default winner (work running vs work already finished), while a non-completion terminal state or absence even from --all keeps it. The skill keeps looking for the older manual handoff, surfacing provenance at the confirm gate when launch evidence is ambiguous.", "files": [], "expectations": [ "The skill screens candidates for the background-launch signature in the producer transcript before surfacing them — glob candidates before short-circuiting, and transcript-discovered file candidates via the same transcript the directive was found in", "It correlates the matched launch with the exact candidate file via the launch prompt's exact Read directive path, never excluding session-wide — a same-session manual handoff remains a valid candidate", "It treats --name continue- slug-only evidence as ambiguous unless it uniquely resolves to one candidate, since same-topic files all match the slug", "It applies the same screening to prompt-only candidates, bound by content: only the rails block whose exact prompt the successful claude --bg launch delivered is excluded — a later launch of a different prompt never disqualifies an earlier manual block in the same transcript", - "A save-point whose correlated background launch succeeded is not presented as the lost handoff; the operator is pointed at claude agents and the search continues", + "A save-point whose correlated background launch succeeded and whose continuation is still live is not presented as the lost handoff; the operator is pointed at claude agents and the search continues", "It treats a launch as successful only with transcript evidence the agent actually appeared — a zero-exit claude --bg alone is unverified and keeps the candidate as ambiguous", - "Before excluding, it rechecks the current claude agents state: a continuation that has since exited or failed keeps the candidate as the restart artifact, since transcript evidence proves only launch-time persistence", + "Before excluding, it rechecks the current agent state with `claude agents --json --all` and does not treat absence from the bare active list as failure: only a terminal state that does not report completion keeps the candidate as the restart artifact, and absence even from --all is reported as UNKNOWN rather than as a failed background attempt", "When launch evidence is ambiguous, unverified, or shows failure, the candidate is kept with its provenance surfaced at the confirm gate" ] }, @@ -166,6 +166,46 @@ "The current repo and any `Handoff origin:` repository remain in the widening set — narrowing the cwd rule never removes the roots that are already verified", "The widening stays read-only and bounded; no machine-wide filesystem sweep is performed under any branch" ] + }, + { + "id": 13, + "name": "completed-continuation-is-not-a-failed-one", + "prompt": "Find my lost handoff. Yesterday I sent a task to a background agent and it finished the work; today I also lost an older handoff prompt I never copied.", + "expected_output": "The recent save-point belongs to the finished background continuation. Rechecking with `claude agents --json --all` shows that session in a terminal state reporting completion — not absent-and-therefore-failed, which is what the bare `claude agents --json` would suggest, since it lists active sessions only. The skill excludes that save-point from the default winner, says the continuation FINISHED and points at its output rather than a rerun, and keeps searching — recovering the older manual handoff the operator actually lost. Had the session been absent even from --all, the outcome would be reported as UNKNOWN (the --all history is bounded) and the candidate kept, never labelled a failed background attempt.", + "files": [], + "expectations": [ + "It reads the continuation's current state with `claude agents --json --all`, not the bare `--json`, because the bare form lists active sessions only and excludes completed background sessions", + "It does NOT report the completed continuation's save-point as a failed background attempt", + "The completed continuation's save-point is excluded from the default winner, with the operator pointed at that session's finished output instead of a rerun", + "Excluding the completed candidate does not end the search — the skill keeps going and surfaces the older manual handoff", + "A session absent even from `--all` is reported as an UNKNOWN outcome and the candidate is kept, rather than asserted to have failed", + "A terminal state that does not report completion still keeps the save-point as the restart artifact, preserving the reason the recheck exists" + ] + }, + { + "id": 14, + "name": "absent-from-all-is-unknown-not-failed", + "prompt": "Find my lost handoff. A while back I kicked off a background agent on the auth-refactor and I never saw what came of it — it's not showing up in claude agents at all now, not even with --all.", + "expected_output": "The skill screens the glob candidates, locates the save-point's producer transcript, and finds the background-launch signature. Rechecking with `claude agents --json --all` returns nothing for that session. It resolves this as UNKNOWN and says so: the `--all` history is bounded, so a long-finished session can age out of it, and a lookup that cannot see a session cannot say why it ended. The candidate is KEPT, and the outcome is reported as undetermined — never as a failed background attempt, and never as a completed one either. If the only evidence tying the candidate to a session is the `continue-` slug rather than a recorded `sessionId`/`id`, the recheck is UNKNOWN regardless of whether that slug currently matches exactly one listed session.", + "files": [], + "expectations": [ + "Absence from `claude agents --json --all` is reported as UNKNOWN, with the bounded `--all` history given as the reason a lookup cannot distinguish aged-out from never-ran", + "The candidate is KEPT rather than excluded, because nothing established that the continuation finished the work", + "The outcome is not asserted to be a failure, and is not asserted to be a completion either — an unseen session supports neither claim", + "Slug-only evidence resolves to UNKNOWN even when the slug currently matches exactly one session: only an exact `sessionId`/`id` match identifies a session for this recheck, because a newer launch reusing the same `continue-` slug can be the one still listed after the original aged out" + ] + }, + { + "id": 15, + "name": "terminal-not-completed-is-the-restart-artifact", + "prompt": "Find my lost handoff. I sent the migration work to a background agent yesterday and I think it got interrupted partway — claude agents --all still shows it, but it never reported finishing.", + "expected_output": "Rechecking with `claude agents --json --all` shows the continuation present in a terminal state that does not report completion. This is the branch the recheck exists for: the skill KEEPS the save-point as the restart artifact, names the observed `state` value rather than forcing it into a familiar label, and hands it to the operator as the place to resume from. It is not excluded from the default winner the way a live or a completed continuation would be, because the work is neither in progress nor done.", + "files": [], + "expectations": [ + "A terminal state that does not report completion KEEPS the candidate, as the restart artifact rather than as a discarded save-point", + "The observed `state` value is reported as read, not coerced into one of the familiar labels — the branch values are observed, not a closed set", + "The candidate is not excluded from the default winner: exclusion is for live and for completed continuations, and this is neither" + ] } ] }