Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion plugins/session-flow/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "session-flow",
"version": "0.17.16",
"version": "0.17.17",
"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 — 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",
Expand Down
40 changes: 40 additions & 0 deletions plugins/session-flow/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,45 @@
# Changelog — session-flow plugin

## [0.17.17]

### Fixed

- **`continue-in-background`'s dirty-tree gate had no branch for a consuming directory that is not
a git repository (melodic-software/claude-code-plugins#929).** The gate opened by running
`git status --porcelain -uall`, which in a non-repo directory (a session started in `$HOME`, say)
fails with `fatal: not a git repository` and leaves the skill with no specified behavior.
- The gate now establishes repository status first with `git rev-parse --is-inside-work-tree`.
Two results are specified and everything else falls through to a deliberate default, so the
gate is exhaustive by construction rather than by enumeration. `true` → inspect the tree as
before. Positively identified as *not a git repository* **and** no `WorktreeCreate` hook
configured → launch: there is no uncommitted work to protect and no worktree isolation to
lose, because background sessions then write to the working directory directly rather than
moving into an isolated worktree (<https://code.claude.com/docs/en/agent-view>); the launch
report states that reading.
- The hook is part of that branch's condition, not a parenthetical premise. `WorktreeCreate` is
the isolation path for non-Git source control
(`plugins/playbooks/skills/boris/reference/worktrees.md`) and "replaces the default worktree
creation entirely" (`docs/conventions/topic-docs/README.md`), so a configured hook moves the
launched session into a workspace the consuming checkout's local changes never reach — exactly
what the dirty-tree gate exists to prevent. Absence of the hook must therefore be
*established*; a configured hook, or an absence that cannot be established, falls to the wide
default below and does not launch.
- Anything else is UNKNOWN tree state, not clean, and does not launch. That default is wide on
purpose: a failure for some other reason (dubious ownership, a damaged repository, git missing
from `PATH`), and also a *successful* `false` — inside a bare repository or a `.git`
directory, where the command exits 0 and there is no work tree. Routing by exit status alone
in either direction would have turned a gate that protects uncommitted work into one that
fails open on exactly the cases where the tree is least readable.
- The context-gathering block's "treat any failure as an unknown value and carry on" is now
scoped to itself. It colors the save-point and is not the gate; its shrug, and its non-`-uall`
`git status` output, must not be carried into the gate, which reads a git failure the opposite
way.
- The post-launch enforcement checklist and the gotchas index carry the same branches, so the
surfaces the user verifies the exit shape against no longer disagree with the gate.
- Four eval cases added: a non-repo directory with no `WorktreeCreate` hook that launches, a
non-repo directory *with* one that does not, a git failure that is not "not a git repository"
that does not, and a zero-exit `false` that does not.

## [0.17.16]

### Fixed
Expand Down
50 changes: 42 additions & 8 deletions plugins/session-flow/skills/continue-in-background/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ Collect these with **individual** Bash calls, one command per call:
- Uncommitted changes — `git status --porcelain`, reading **at most the first 20 entries**
- Recent commits — `git log --oneline -5`

Treat any failure as an unknown value and carry on. These are gathered here rather than pre-computed
Treat any failure as an unknown value and carry on — this block only colors the save-point, and
nothing here is the dirty-tree gate. That gate runs its own commands at delivery step 1 and reads a
git failure as a reason NOT to launch; never carry this block's shrug, or its non-`-uall`
`git status` output, into it. These are gathered here rather than pre-computed
because a worktree-isolated agent refuses any command carrying a `$`-expansion, which made this skill
fail at load — keep `$`-expansion out of the pre-compute block (#1687).

Expand Down Expand Up @@ -68,7 +71,32 @@ directive to the handoff file; prompt-only: the remaining-work bullets travel in
The rails prompt from the engine doc is still emitted FIRST (transparency + manual fallback),
then:

1. **Dirty-tree gate.** Run `git status --porcelain -uall` in the consuming project (`-uall`
1. **Dirty-tree gate.** First establish there is a tree to inspect, with
`git rev-parse --is-inside-work-tree` in the consuming project. Exactly two results are
specified, and everything else falls through to a deliberate default:

- Prints `true` → there is a work tree; inspect it with the gate below.
- Fails *specifically* because this is not a git repository, AND no `WorktreeCreate` hook is
configured (<https://code.claude.com/docs/en/hooks>) → there is no uncommitted work to
protect and no worktree isolation to lose, since background sessions then write to the
working directory directly rather than moving into one
(<https://code.claude.com/docs/en/agent-view>) → launch, and state both in the launch
report. That hook is the isolation path for non-Git source control, so a configured one —
or an absence you cannot establish — puts the launched session in a workspace this
checkout's local changes never reach: that is the default branch below, not this one.
- **Anything else** → the tree's state is UNKNOWN, which is not the same as clean → do NOT
launch; fall back exactly as the dirty case does below, reporting what the command said.
"Anything else" is the default on purpose, and it is wide: a failure for some other reason
(dubious ownership, a damaged repository, git missing from `PATH`), and also a *successful*
`false` — inside a bare repository or inside a `.git` directory, where the command exits 0
and there is no work tree to inspect.

Never route by exit status alone. A non-zero exit is not evidence of "no repository", and a
zero exit is not evidence of a clean tree; both readings put dirty trees on the launch path,
which is how a gate that exists to protect uncommitted work ends up failing open. Only the
two identifications above may leave the default branch.

Inspect the tree with `git status --porcelain -uall` in the consuming project (`-uall`
lists files inside untracked directories individually; the default collapses a brand-new
handoff directory into one directory entry, which both defeats the exemption below and can
hide other dirt behind it) and IGNORE save-point files under the handoff location — the
Expand Down Expand Up @@ -138,9 +166,13 @@ doc's save-point items, which the sibling `handoff` skill's checklists mirror):

- [ ] Explicit user intent for background delegation verified (hard gate) — absent intent →
save-point + `/clear`-then-paste exit, no launch, reason stated
- [ ] Dirty-tree gate evaluated (`git status --porcelain -uall` this turn, ignoring save-point
files under the handoff location); other uncommitted changes without the linked-worktree
exception → no launch, reason reported, fallback to `/clear`-then-paste
- [ ] Dirty-tree gate evaluated this turn: `git rev-parse --is-inside-work-tree` first, then
`git status --porcelain -uall` when it says `true`, ignoring save-point files under the handoff
location; other uncommitted changes without the linked-worktree exception → no launch, reason
reported, fallback to `/clear`-then-paste. Positively identified as not a git repository AND no
`WorktreeCreate` hook configured → launch, that reading stated in the report; any other
`rev-parse` result, failing or `false`, and any hook whose absence is not established → state
unknown, no launch, same fallback
- [ ] Background agent launched with the rails prompt (`claude --bg --name …`) and the launch
result reported (including any non-inherited flags mirrored or worth flagging) — OR the
non-zero exit reported with fallback to `/clear`-then-paste
Expand All @@ -149,9 +181,11 @@ doc's save-point items, which the sibling `handoff` skill's checklists mirror):
## Gotchas

Failure patterns are documented inline at the step that owns them: the `-uall` untracked-directory
collapse (dirty-tree gate, step 1), the no-inline-prompt rule and the session-persistence env
requirement (launch command, step 2), slug sanitization ("Arguments"), and non-inheritance
surprises — model, effort, CLI flags ("What the launched session inherits").
collapse, the non-repo-plus-no-`WorktreeCreate`-hook vs. unknown split with its deliberately wide
unknown default, and the ban on reusing the context block's git output (dirty-tree gate, step 1;
context block), the no-inline-prompt rule and the session-persistence env requirement (launch
command, step 2), slug sanitization ("Arguments"), and non-inheritance surprises — model, effort,
CLI flags ("What the launched session inherits").

## What this skill does NOT do

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,56 @@
"It does NOT fall back to /clear-then-paste on account of the dirty tree",
"The launch result is reported and the skill STOPS — no monitoring or babysitting"
]
},
{
"id": 5,
"name": "non-repo-directory-launches",
"prompt": "/continue-in-background — I'm done for the night, hand it to a background agent. (Note: this session was started in my home directory, which is not a git repository at all — git rev-parse --is-inside-work-tree fails with 'fatal: not a git repository' — and no WorktreeCreate hook is configured in any settings scope that applies here.)",
"expected_output": "The dirty-tree gate identifies the consuming directory as not a git repository — not merely a failed command — and establishes that no WorktreeCreate hook is configured, so the non-repo branch applies: there is no uncommitted work to protect and no worktree isolation to lose, because the background session writes to the working directory directly. It launches the background agent, states that reading in the launch report, and STOPS.",
"files": [],
"expectations": [
"The skill establishes repository status first (git rev-parse --is-inside-work-tree) rather than running git status --porcelain -uall blind and stalling on 'fatal: not a git repository'",
"It identifies the non-repo case positively AND establishes that no WorktreeCreate hook is configured before launching the background agent",
"The launch report states the non-repo reading: no uncommitted work to protect, and the launched session writes to the working directory directly instead of moving into an isolated worktree",
"The skill STOPS after the launch report — no monitoring, polling, or new work items"
]
},
{
"id": 6,
"name": "unknown-tree-state-does-not-launch",
"prompt": "/continue-in-background — keep it moving, I'm away. (Note: git rev-parse --is-inside-work-tree exits non-zero with 'fatal: detected dubious ownership in repository at ...' — this IS a repository, git just refuses to read it.)",
"expected_output": "The gate's third branch applies: git failed for a reason other than 'not a git repository', so the tree's state is unknown rather than clean. The skill does NOT launch — it reports the git failure and falls back to the standard /clear-then-paste instruction, exactly as it does for a dirty tree.",
"files": [],
"expectations": [
"The skill does NOT treat a non-zero exit as the non-repo branch — the failure is not 'not a git repository', so the tree may be dirty and cannot be proven clean",
"It does NOT launch the background agent: unknown tree state is handled like dirty state, not like clean state",
"It reports what the command said and falls back to the standard /clear-then-paste instruction"
]
},
{
"id": 7,
"name": "no-work-tree-despite-zero-exit-does-not-launch",
"prompt": "/continue-in-background — hand this to a background agent, I'm off. (Note: the working directory is a bare repository, so git rev-parse --is-inside-work-tree exits 0 and prints 'false'.)",
"expected_output": "A zero exit is not evidence of a clean tree. The command printed 'false', not 'true', and this is not the positively-identified non-repo case either, so it lands in the gate's unknown default: the skill does NOT launch, reports that there is no work tree to inspect, and falls back to the standard /clear-then-paste instruction.",
"files": [],
"expectations": [
"The skill does NOT read the zero exit status as a passing gate — the printed value is 'false', so there is no work tree and nothing was proven clean",
"It does NOT take the non-repo launch branch either: 'false' is not the 'not a git repository' identification, so the permissive branch does not apply",
"It does NOT launch the background agent, and falls back to the standard /clear-then-paste instruction with the reason reported"
]
},
{
"id": 8,
"name": "non-repo-with-worktreecreate-hook-does-not-launch",
"prompt": "/continue-in-background — I'm heading out, hand it to a background agent. (Note: this is a Mercurial checkout, so git rev-parse --is-inside-work-tree fails with 'fatal: not a git repository', but settings.json defines WorktreeCreate and WorktreeRemove hooks so sessions get isolation without Git. I have uncommitted hg changes here.)",
"expected_output": "'Not a git repository' alone does not open the launch branch. A configured WorktreeCreate hook is the non-Git isolation path, so the launched session moves into a hook-created workspace that this checkout's local changes never reach — the same loss the dirty-tree gate exists to prevent. The case lands in the gate's unknown default: the skill does NOT launch, reports why, and falls back to the standard /clear-then-paste instruction.",
"files": [],
"expectations": [
"The skill does NOT launch merely because git rev-parse reported 'not a git repository' — it checks whether a WorktreeCreate hook is configured before taking that branch",
"It recognises that a configured WorktreeCreate hook means the launched session works in an isolated workspace, so uncommitted local changes would not carry into it",
"It does NOT launch the background agent, and falls back to the standard /clear-then-paste instruction with the reason reported",
"An inability to establish the hook's absence is treated the same way — unknown, not clean, no launch"
]
}
]
}