finish-the-job
The hands-off finish protocol. When a user hands you a goal — in Slack, in a CLI turn, or via a cron task that wasn't finished — this skill is the single pipeline that drives it to a verifiable conclusion. It composes existing primitives (/fs, /f, workflow/drive-pr-to-green, workflow/always-pr-never-local-edit) so the assistant stops halfway never.
Why this skill exists
Three drift patterns were observed in the user's last week of Slack threads (2026-06-12 to 2026-06-19, C0AH3RY3DK6 / C09GRLXF9GR):
- "Ack + design prose + silence" — agent acknowledges, writes 200 lines of design options, asks one more clarifying question, never executes. The dropped-thread-followup cron fires 4h later.
- "Started + fork + multi-option question" — agent reads files, makes a local commit, hits a judgment call, posts a 3-option menu. User doesn't reply (busy). Thread goes cold.
- "Investigation without end-state" — agent reads 6 files, posts "Here's what I found: …" with no PR, no commit, or dry-run. The "I'm waiting for the right moment to ship" trap.
The pattern in all three: the agent stopped at a place that required the user to make a decision or supply a follow-up, instead of making the call itself and posting the result. The user's explicit rule (2026-06-19): "I am ok with outcomes that aren't my goal as long as they are correct ie. Green PR, real evidence, like correct but misinterpret is fine but stopping halfway is not."
Contract
When this skill fires, the work is not done until ONE of these end-states is provably true:
| End-state | Proof artifact |
|---|
| Green PR merged | gh pr view <N> --json state = MERGED + Green Gate workflow log gate-by-gate PASS + non-unit-test evidence bundle URL |
| PR open with green CI awaiting user merge | gh pr view <N> --json mergeStateStatus,reviewDecision shows MERGEABLE + review clean; ONE-LINE message naming PR URL + the one gate the user must clear |
| Local state change verified | git diff + git log --oneline -3 + the actual test run output captured in the final reply (not described — shown) |
| Dry-run to local machine | The exact commands the user would run, executed against a fresh worktree, output captured; the user can paste the same commands and get the same result |
NOT acceptable end-states:
-
❌ "Here's the design, want me to ship it?" — that's design-proposal-and-silence
-
❌ "Tests pass locally, PR is ready, want me to push?" — that's local-commit-and-ask
-
❌ "Investigation complete, here are the findings" without a commit, PR, or dry-run
-
❌ "I've started the worker, will update when done" — that's ack-and-walk-away
-
❌ Mid-stream question without first exhausting the LLM's own judgment (per the user's rule: "in the middle I want AI to use its best judgement")
-
❌ "Stale-PR-branch rebase when ao spawn is down" (added 2026-07-14, $GITHUB_REPOSITORY PR #8290). When the user asks for fullrun on a PR that is mergeable=CONFLICTING AND ao spawn returns Internal server error, the right move is NOT to stop at "AO is down, can't dispatch" and NOT to spend the entire budget on a fresh branch. Per the AO-spawn pivot reference (references/ao-spawn-internal-error-pivot-2026-07-12.md), execute the conflict resolution inline in the gateway session using the verified recipe in references/stale-pr-branch-rebase-conflict-2026-07-14.md. Key mechanics: (a) use git merge (not rebase) to preserve the PR's review history when the PR has substantive CodeRabbit/Bugbot review evidence; (b) --theirs and --ours semantics FLIP between merge and rebase — read the conflict markers first (grep -nE "<<<<<<< |=======|>>>>>>>"), do NOT guess; (c) DO NOT use set -e in chained conflict-resolution commands — its first non-zero exit kills the script before git commit fires, leaving the worktree half-resolved; (d) push to the PR's original branch with --force-with-lease (not --force) to preserve PR identity; (e) after your push, GitHub may auto-merge main into the branch (visible as a merge origin/main into <branch> commit) — your SHA changes, but the changes are still in history as an ancestor. Verified PR #8290 went from CONFLICTING → MERGEABLE+CLEAN in 4 commands + 1 push, all inline in the gateway session.
-
❌ "60-min clarify silence is not a license to stop pushing" (added 2026-07-14, jleechanorg/claude-commands PR #328 + $GITHUB_REPOSITORY PR #8402). When the agent asks a Phase 0 clarification question and the user does not answer within ~60 minutes, the agent's instinct is to either re-ask, proceed locally and stop, or post a status update. Silence is the user being busy / asleep / in a meeting, not authorization to halt mid-stream. Per , the right move when scope is even moderately unambiguous is to drive to PR-merged end-state ( + + + Slack reply with PR URL) without re-asking. Verified bug case 2026-07-14: agent asked the clarify, no answer came, agent executed the minimal-scope interpretation LOCALLY but never reached . The user came back ~22h later with "why didn't you just do it without stopping?" — the entire 22h gap was avoidable. Recipe: (a) classify scope as "obvious enough to act on" vs "needs the user's call" — when scope can be inferred from prior session context + recent edits, it is obvious enough; (b) make the conservative interpretive call (do less, not more, but DO it); (c) drive to PR-open end-state in the same session — + + (no "want me to push?" confirmation gate); (d) post the Slack reply with PR URLs as the final reply. Companion reference: with the two-symlink-repo tracing recipe ( for user-scope commands vs for repo-local commands) + the push-verified check + the symlink-trap pitfall ( resolves to ).
Sub-case (added 2026-07-28, same PR #8661 incident): the worker's python3 -m unittest call returned 54 passed, 1 failed with ModuleNotFoundError: No module named 'jsonschema'. The failure was not a campaign-contract failure — it was the worker importing the full mvp_site chain (mvp_site.agent_prompts → $PROJECT_ROOT/dice_strategy.py → $PROJECT_ROOT/llm_providers/provider_utils.py → jsonschema) against the system Python, which lacks the transitive dependencies that only live inside the repo's venv/. This is the same trap family as always-pr-never-local-edit v1.5.0 "Worktree-silent-edit trap" — a fresh worktree lacks the repo's environment, and any test that imports the repo's source against the system Python fails on a transitive dep that exists only inside the repo's venv. Detection: ls -la <worktree>/venv/bin/python 2>/dev/null (does the worktree have its own venv?) OR ls -la ~/.hermes/projects/<repo>/venv/bin/python 2>/dev/null (is the canonical repo venv reachable from this host?). Mitigation in the spawn brief: prepend source ~/.hermes/projects/<repo>/venv/bin/activate (canonical repo venv) or source <worktree>/venv/bin/activate (in-worktree venv) before any python3 -m unittest ... call; if the dep is still missing, pip install -r requirements.txt inside the same venv first. For $GITHUB_REPOSITORY specifically: the canonical venv is ~/.hermes/projects/your-project.com/venv/. pip install -r requirements.txt inside it once, then every subsequent worker run inherits the dep set. Cross-ref always-pr-never-local-edit v1.5.0 "Worktree test-import via Path.home() quirk" for the same pattern in a different shape.
-
❌ "Research-only reply stops at synthesis without doing the install / configure / dry-run the user asked for" (added 2026-07-30, Slack C09GRLXF9GR/p1785467202). When the goal is "research X and use it" (e.g. /research all of these and see how/if we should use them in combo), the deliverable is NOT a Slack post summarizing findings + asking "want me to install?". The deliverable is the synthesis AND the install + verify pass + documented state of what landed and what got auto-configured. User signal (verbatim, 2026-07-30): "Ok will did you even finish the work?". For a "research + use" goal the correct end-state is "local state change verified": install log + inventory + audit of auto-configured items (~/.claude/settings.json hooks, enabledPlugins, SOUL.md / CLAUDE.md mtimes). Sub-case + the 15-hook inventory + the --profile=core recipe for GSD Core v1.9.0 in references/research-then-install-verify-2026-07-30.md. NEVER post "research complete" without the install + verify pass when the user's goal includes the verb "use".
-
❌ "User asks the background worker to 'update this thread every minute' — workers cannot post to Slack; the gateway session is the only thread-poster" (added 2026-07-28, Slack C0AH3SD5C79). A background claudem / ao spawn worker runs in its own process with terminal + filesystem access only. It has no Slack MCP tool, no Slack-thread identity, and no way to call mcp__slack__conversations_add_message with the user's thread_ts. When the user asks "tell it to update this thread every minute," the correct answer is structural: the worker cannot do that. The gateway session that spawned it is the only entity that can post. Recipe: (a) state the limitation directly in the thread — "the worker has terminal + filesystem only; it cannot post to this thread"; (b) offer the alternative — "I will post updates here every N minutes from this session by polling process(action='poll') + git -C <wt> status --short"; (c) execute that polling cadence from the gateway session for the duration of the worker run; (d) when the worker completes, post the final state (process exit reason + git log --oneline origin/main..HEAD + test output + PR URL) in a single terminal reply. Anti-pattern: silently letting the worker run and hoping the user can see it from another channel — the user cannot, and "will I actually see updates in this thread or will you never update it again?" is the canonical sign of this gap. The same limitation applies to ALL background workers (claudem / ao spawn / opencode / Codex / openhands) — none of them have Slack-thread identity unless explicitly wired up at the gateway level. Cross-ref the canonical mechanical-closeout recipe at (worked example: @ , $GITHUB_REPOSITORY PR #8661, 6 files / +817 / -0, 54/55 tests pass — the one failure was the worker-import-environment blocker below, NOT a campaign-contract failure).
Sub-case (added 2026-07-28, same PR #8661 incident): the worker's python3 -m unittest call returned 54 passed, 1 failed with ModuleNotFoundError: No module named 'jsonschema'. The failure was not a campaign-contract failure — it was the worker importing the full mvp_site chain (mvp_site.agent_prompts → $PROJECT_ROOT/dice_strategy.py → $PROJECT_ROOT/llm_providers/provider_utils.py → jsonschema) against the system Python, which lacks the transitive dependencies that only live inside the repo's venv/. This is the same trap family as always-pr-never-local-edit v1.5.0 "Worktree-silent-edit trap" — a fresh worktree lacks the repo's environment, and any test that imports the repo's source against the system Python fails on a transitive dep that exists only inside the repo's venv. Detection: ls -la <worktree>/venv/bin/python 2>/dev/null (does the worktree have its own venv?) OR ls -la ~/.hermes/projects/<repo>/venv/bin/python 2>/dev/null (is the canonical repo venv reachable from this host?). Mitigation in the spawn brief: prepend source ~/.hermes/projects/<repo>/venv/bin/activate (canonical repo venv) or source <worktree>/venv/bin/activate (in-worktree venv) before any python3 -m unittest ... call; if the dep is still missing, pip install -r requirements.txt inside the same venv first. For $GITHUB_REPOSITORY specifically: the canonical venv is ~/.hermes/projects/your-project.com/venv/. pip install -r requirements.txt inside it once, then every subsequent worker run inherits the dep set. Cross-ref always-pr-never-local-edit v1.5.0 "Worktree test-import via Path.home() quirk" for the same pattern in a different shape.
Mechanical-closeout prompt template (verified 2026-07-28, PR #8661)
When a worker has already produced the intended edits and the next spawn only needs to commit + push, write the brief in this exact shape — generic "continue from here and finish" briefs always re-evaluate:
You are continuing from the existing edits in <worktree>. Do NOT analyze.
Do NOT re-discover. Do NOT inspect unrelated files. Execute a mechanical
closeout now:
1. `git status` and `git diff --stat` to confirm the intended files are present.
2. `git add <exact file1> <exact file2> ...` — the ONLY files named in the
prior worker's diff plus any untracked files in the intended locations.
Do not use `git add -A` or `git add .`.
3. `python3 -m py_compile <python files>` for syntax check.
4. Run the focused test file: `python3 -m unittest <module path> -v`.
Use the repo venv (source ~/.hermes/projects/<repo>/venv/bin/activate)
before invoking python3 so transitive deps resolve.
5. `git commit -m "claude/minimax-M3: <short subject>"` — subject prefix
mandatory per the commit-provenance rule.
6. `git push origin HEAD:refs/heads/<branch>`.
7. Report: exact commands run, exact output, exact commit SHA, exact
remote SHA, exact test summary. If a test fails, still commit and
report the failure.
Do NOT create a PR. The gateway session owns PR creation and Slack-thread
updates.
The brief that failed both times before was "Continue from the existing edits and finish" — workers burned the budget on re-evaluation. The brief that landed was "Do not analyze, do not re-discover, here are the exact files to add, here is the exact commit prefix, here is the exact end-state." Discover / edit / closeout are three different jobs — do NOT mix them in one spawn.
Checkpoint cadence from the gateway session
Per the claude-code-claudem skill v1.6.0 "Worker scope vs gateway scope" pitfall, the worker cannot post to Slack; the gateway session must poll. For background-worker runs that the user wants visible:
echo "=== $(date -u +%FT%TZ) — process status ==="
process_id=<process-id-from-spawn>
ps -p $process_id -o pid,etime,cmd 2>/dev/null || echo "process exited"
git -C <worktree> status --short --branch
git -C <worktree> diff --stat
git -C <worktree> log --oneline origin/main..HEAD 2>/dev/null | head -10
The "kill on empty + elapsed > 60s" rule is the operationalization of the bullet above — without it, the worker burns the full --max-turns budget on re-evaluation and exits with no code change.
- ❌ "GitHub REST + GraphQL rate-limit simultaneously at 0 on user ID 13840161 — schedule a one-time cron to re-verify, do NOT keep hammering the API" (added 2026-07-26, $GITHUB_REPOSITORY#8623 follow-up). Verified pattern:
gh api rate_limit --jq '{core_remaining: .resources.core.remaining, graphql_remaining: .resources.graphql.remaining}' returns core_remaining: 4245, graphql_remaining: 0 (or core_remaining: 0, graphql_remaining: 0 on subsequent retries), even though the user has thousands of unspent requests in the core bucket. Symptom: gh pr view, gh api repos/.../pulls/<N>, gh api graphql -f query=... all return 403 API rate limit exceeded for user ID 13840161 with request_id E540:... / E789:... style IDs. The bucket the GH API marks is sometimes the user-ID anti-abuse bucket, not the documented core/graphql pair. Recipe: (a) STOP the API hammering after 2 retries (the rep is real); (b) verify durable state locally (git rev-parse origin/<branch>, the new commit SHA, the test harness); (c) post the result to Slack with the durable-state proof + the cron job ID that will re-verify. The user's rule across the SOUL.md ## LOAD commit family is that proof is required, but local SHA + remote SHA + test pass IS proof even when the GH API is blocked. Verified cron recipe:
cronjob action=create \
--schedule "25m" \
--name "issue-8623 pr-24 verification (25m)" \
--deliver "slack:" \
--prompt "<re-verify instructions: gh api rate_limit + git rev-parse origin/<branch> + gh api repos/<owner>/<repo>/pulls/<N> + gh api commits/<sha>/check-runs + bash <test-harness>>" \
--repeat 1
The next session's first turn (or this session's next turn after the rate-limit window) re-runs the verification and posts the result. Do NOT let the agent stall on "iteration budget exhausted" producing no Slack reply — push-pr-donot-stop-halfway covers the abstract rule; this is the GitHub-side operationalization.
Pitfall P_research_stop — see references/research-then-stop-and-gsd-install-2026-07-30.md for the full pitfall text + GSD Core install findings.
Phases (execute in order, no pauses between)
Phase 0 — Classify the goal (one decision, ≤30 seconds)
Classify the user's goal into ONE of:
| Goal shape | Examples | Routing |
|---|
| PR fix | "fix the CI on PR #N", "/green this PR", "address CodeRabbit on PR #N" | workflow/drive-pr-to-green |
| New code / new feature | "add X to the repo", "implement Y", "build a Z" | /fs then /f (feature-mode) |
| New PR for existing work | "open a PR for my branch", "ship my changes", "merge my draft" | workflow/always-pr-never-local-edit |
| Investigation / read-only | "find out which key leaked", "what does X do", "review my plan" | Inline research → answer with proof artifact (file:line + quoted text + reproducible command) |
| Ops / config / infra | "rotate the key", "bump the Cloud Run memory", "fix the daily cron" | Inline gcloud/kubectl/etc. with output captured; if a code PR is also needed, file as follow-up |
| Meta / about-Hermes | "skillify X", "make this a skill", "improve Y workflow" | skillify skill |
| Learn + skillify + harness closeout (added 2026-07-14) | "/learn and /skillify", "/harness and then fix it", "/newb" + "fresh worktree skill" | Load all four skills; sequence learn → skillify → harness → fresh-worktree verify |
| Disable / stop / remove something (added 2026-07-20) | "stop MCP mail from X", "stop the bot from X", "stop X from giving me Y", "stop the cron" | Phase 0.5 disambiguation required — trace symptom provenance to actual source before removing the literal target. See harness-postmortem Phase 0 wrong-target-removal-on-stop-X-from-Y working class + reference references/wrong-target-removal-stop-X-from-Y-2026-07-20.md. If unsure between upstream producer vs downstream consumer, ASK ONE QUESTION — do NOT silently remove the literal noun. |
| Patch-bundle apply from Slack/upload (added 2026-07-20) | "apply this patch", "use /super to code this", "review and apply infra-XX patch", "git am this" | Phase 0.5b path-validity pre-flight, BEFORE acking the user. Run git apply --check from the actual repo cwd, then if the patch fails, run the (author email domain, base SHA on target repo, source-repo github existence, every path present in target repo HEAD). See for the cwd gate + misroute-detection probes + the right ack shape + cleanup recipe. Cross-fork misroute is the github-level analog of the wrong-target-removal pattern (Phase 0.5 disambiguation, "stop X from Y"). The earlier "On it — applying" ack pattern was a premature ack — verify applicability FIRST, ack SECOND. |
If the classification is ambiguous after 30 seconds, ASK ONE QUESTION (the only question in this whole pipeline). The user is willing to invest up-front in Q&A specifically to avoid mid-stream steering. Use clarify.
Phase 1 — /fs first if the goal is non-trivial
Trigger /fs if ANY of these are true:
- Goal is a new feature or non-trivial refactor (not a 1-line fix)
- Goal mentions multiple components, files, or repos
- Goal has ambiguous wording that the agent could misinterpret in 2+ ways
- Goal is a design task the user wants reviewed
/fs produces spec.md + attractor_spec.md, both codex-cold-reviewed, before any code is written. The user's up-front Q&A investment pays off here — by the time the worker starts, the spec is unambiguous.
Skip /fs if:
- Goal is a PR fix on an existing branch (the PR diff IS the spec)
- Goal is <50 lines of mechanical change
- Goal is investigation / read-only (no code to spec)
Phase 2 — Dispatch (do not self-execute multi-step code work)
For PR fixes: load workflow/drive-pr-to-green and follow its full sequence (worktree at explicit SHA → fix → push → watch CI → clear review → self-merge when authorized).
For new features: dispatch via dispatch-task skill (ao spawn) so the worker gets its own tool-call budget. Inline gateway sessions cap at ~25 tool calls; AO workers have their own budget.
When ao spawn returns Internal server error despite a healthy daemon (ao doctor shows ready + active workers), do NOT keep retrying. Pivot to inline execution per references/ao-spawn-internal-error-pivot-2026-07-12.md. For the conflict-resolution variant (stale PR branch, must preserve PR identity), see references/stale-pr-branch-rebase-conflict-2026-07-14.md — verified on PR #8290.
For new PR from local branch: workflow/always-pr-never-local-edit → fresh worktree from origin/main → port the local diff if needed → push → gh pr create.
For ops/investigation: execute inline (gcloud, curl, file reads). The "inline-able" boundary is one tool call OR a tight sequence with no fork.
For learn + skillify + harness closeout (added 2026-07-14): execute all four actions in order. Do NOT skip any — the user named them together for a reason.
Phase 3 — Drive to conclusion
The dispatched worker OR inline execution runs until one of the end-states in the Contract is provably true. If the worker hits a fork mid-stream:
- Apply the user's rule: make the call yourself, surface it in the final reply ("I picked X over Y because Z; if you wanted Y, here's the one-line revert").
- Never post a multi-option question to the user mid-stream. The exception is Phase 0 — that's up-front Q&A, which is allowed.
- If the fork is truly unrecoverable without user input (e.g. force-push authorization, secrets the agent can't see, env-specific config only the user has), halt with the ONE-LINE BLOCKER shape: "PR #N is at ; one blocker: ."
Phase 4.5 — PR draft and CI truthfulness gate
When the requested end state is “push a draft PR,” distinguish remote reviewability from green CI. A pushed branch is not a PR, and a created draft PR is not green.
Before the final reply:
- Verify the branch's exact remote SHA (
git ls-remote or REST ref lookup).
- Verify the PR exists through an independent read (
gh pr view or REST GET /pulls/<number>), including state, draft, headRefName, and head.sha.
- Verify ancestry and scope against
origin/main; do not rely on the worker's summary.
- Query check runs directly. Report
success, failure, pending, and skipped separately. mergeable_state=clean means no conflict, not passing CI.
- If the PR is draft and required checks are skipped, use the end-state wording “draft PR pushed; CI/evidence incomplete”. Do not say “green,” “ready,” or “all checks pass.”
- Resolve the exact PR body to a file and run the outbound-secret gate before any PR create/comment transport. A body reconstructed from memory is not the artifact that was sent.
If GraphQL PR creation hangs or is rate-limited, use the REST fallback documented in dispatch-task/references/rest-pr-create-rate-limit-fallback.md; verify the resulting URL and SHA with a second REST read before claiming completion.
Phase 4.6 — Explicitly separate fix, detector, and evidence blocker
For bug investigations that produce a proposed PR, classify each deliverable as one of:
- Root-cause fix — changes the mechanism that causes the defect.
- Detector/observability — reports the defect without changing behavior.
- Evidence/contract guard — prevents regression or proves an invariant.
A detector must not be reported as the full fix. If the authoritative write/backfill path is unproven, say so directly in the PR body and final report, and name the next evidence required (for example, real-server + real-LLM + BQ capture). This prevents a log-only validator from being mistaken for a state repair.
Every completion reply MUST contain:
- End-state declaration — "✅ Done: <green PR #N merged> | <PR #N open + green, awaiting your review> | "
- Proof artifact — PR URL,
gh pr view JSON, or git log + git diff --stat output, or the actual command output captured
- What was decided mid-stream (if anything) — every judgment call the agent made instead of asking, with one-line rationale
- No follow-up question — "want me to X?" is the violation. The work is done; the user reviews.
Anti-patterns (do not do)
-
❌ "I started the worker, will update when done" — the agent has 25 calls; the worker has its own budget. The reply IS the worker. If you have to wait, write the cron babysit reference (see babysit-openclaw skill) and post a status link.
-
❌ "Here's a design with 3 options, which would you like?" — that's Phase 0 question-count inflation. ONE option (your best judgment) + the path forward. The user's rule: "correct but misinterpret is fine."
-
❌ "Local commit + ask 'want me to push?'" — always-pr-never-local-edit is in the same skill family; do not violate it.
-
❌ "Tests pass locally, opening PR now" (then going silent) — the PR URL goes in the final reply, not in a follow-up.
-
❌ "Investigation complete, here are 6 findings" — every finding needs a "what to do about it" line, and at least one finding must be acted on.
-
❌ Stopping at "I asked AO to spawn a worker" — that's an ack. The work isn't done until the worker reports OR the cron takes over.
-
❌ "AO spawn returned Internal server error → gave up" (added 2026-07-14). The 2026-07-12 pivot reference (references/ao-spawn-internal-error-pivot-2026-07-12.md) is the canonical recipe for this wall. The "conflict-resolution variant" recipe (stale PR branch + ao spawn down) is at references/stale-pr-branch-rebase-conflict-2026-07-14.md. Verified PR #8290: ao spawn returned INTERNAL_ERROR on first try → pivoted to inline merge + push → PR went from CONFLICTING to MERGEABLE+CLEAN in one session.
-
❌ "Used set -e in the conflict-resolution chain" (added 2026-07-14, PR #8290). set -e exits the shell on the first non-zero return code. git commit --no-edit returns 0 only if a commit was actually created; if a prior step in the chain returned non-zero, the script aborts BEFORE the commit fires, leaving the worktree half-resolved. Fix: drop set -e and use explicit && chaining or || true on non-fatal commands.
-
❌ "Is X finished? → redo X from scratch" (added 2026-06-28). When the user asks whether a recent non-trivial task was finished, do NOT re-pull gog / re-run searches / regenerate the report from scratch. Use + to surface the prior session's final assistant text in one turn. The 2026-06-28 audit-recovery case reconstructed a 67-message / 1.55M-cache-token prior session in ~10K tokens by exporting session . ~150x cheaper, same answer, one reply. the prior session's final text ends in a multi-option menu (it stalled) OR the underlying data has gone stale (the user said "verify it's still correct" not "is it finished"). See for the 3-step recipe and decision matrix.
Loader / auto-fire contract
This skill is registered in ~/.hermes_prod/skills/RESOLVER.md and the ## COMMIT: finish-the-job block in SOUL.md makes it load automatically for any user message that contains a goal phrase ("can you X", "please Y", "make Z", "investigate A", "fix B"). The trigger phrases are listed in the YAML frontmatter at the top of this file.
When auto-fired: Phase 0 runs first. If classification returns PR-fix / new-code / new-PR, the skill proceeds autonomously. If classification returns investigation / ops, the skill executes inline and posts the final reply with proof. If classification returns learn + skillify + harness closeout (added 2026-07-14), all four sub-actions execute in order.
When explicitly invoked (/finish <goal>): Same as auto-fire, but the user has signaled they want this pipeline regardless of the goal shape.
Deploy sync awareness (read this before rolling out a finish-the-job artifact)
scripts/deploy.sh Stage 4.5 only syncs POLICY_FILES=(CLAUDE.md SOUL.md TOOLS.md HEARTBEAT.md). It does NOT sync skills/ or skills/RESOLVER.md. A skillify pass that creates ~/.hermes_prod/skills/<name>/ works locally, but:
- If you only wrote to prod, the staging git checkout at
~/.hermes/skills/<name>/ is empty — a future git pull --ff-only won't reintroduce it.
- If you wrote to staging only, the prod resolver won't see the skill —
~/.hermes_prod/skills/RESOLVER.md won't have the trigger entry.
- If you wrote both, you still need a manual
cp ~/.hermes/SOUL.md ~/.hermes_prod/SOUL.md (the symlink at ~/.hermes/SOUL.md → ~/.hermes/workspace/SOUL.md lands in the staging tree; deploy copies it to prod) — UNLESS you run deploy.sh end-to-end and accept the canary + restart.
The skillify anti-pattern guard (run in the same turn as any rollout claim):
echo "1. SKILL.md: $(test -f ~/.hermes_prod/skills/<name>/SKILL.md && echo PRESENT || echo MISSING)"
echo "2. tests pass: $(cd ~/.hermes_prod/skills/<name>/tests && python3 -m pytest -q 2>&1 | tail -1)"
echo "3. cron executable: $(test -x ~/.hermes/scripts/<script>.sh && echo YES || echo NO)"
echo "4. plist template: $(plutil -lint ~/.hermes/launchd/<label>.plist.template 2>&1 | tail -1)"
echo "5. RESOLVER entry: $(grep -c '^## <name>$' ~/.hermes_prod/skills/RESOLVER.md) match"
echo "6. resolver triggers: $(grep -c '<user-phrase>' ~/.hermes_prod/skills/RESOLVER.md) match"
echo "7. SOUL.md staging: $(grep -c '^## COMMIT: <name>$' ~/.hermes/SOUL.md)/1"
echo "8. SOUL.md prod: $(grep -c '^## COMMIT: <name>$' ~/.hermes_prod/SOUL.md)/1"
echo "9. SOUL.md in sync: $(diff -q ~/.hermes/SOUL.md ~/.hermes_prod/SOUL.md >/dev/null && echo YES || echo DRIFT)"
Test portability (CodeRabbit MAJOR, 2026-06-19): the test file tests/test_finish_the_job_contract.py uses HERMES_PROD_SKILLS (env var, defaults to $HERMES_HOME/skills) instead of a hardcoded $HOME/... path. Run the tests with:
cd ~/.hermes/skills/finish-the-job/tests && python3 -m pytest -q
HERMES_HOME=~/my-hermes HERMES_PROD_SKILLS=~/my-hermes/skills/finish-the-job python3 -m pytest -q
If items 1-7 land in the same turn as the rollout and 8-9 land within the next deploy cycle, the work is done. Anything outside that pattern is a half-finished rollout — apply the same anti-pattern audit you'd apply to a PR.
Related skills — load order when this fires
dark-factory (always — for the /f and /fs definitions)
drive-pr-to-green (only if goal shape is PR-fix)
always-pr-never-local-edit (only if goal shape is new-PR or local-changes-exist)
dispatch-task (only if Phase 2 decides to dispatch via ao spawn)
dropped-messages (only if the goal was itself a dropped-thread recovery — meta-finish)
session-history-search (only if the user's question is "is X finished?" — reconstruct from prior session before redoing work; see references/reconstruct-from-prior-session-2026-06-28.md)
pr-cleanup-replay (added 2026-07-14 — only if goal is a polluted-PR cleanup; Phase -1 prevention + Strategy A/B recovery + references/gitleaks-pre-push-hook-bypass.md for the 4259-leaks hook bug)
references/patch-bundle-cwd-preflight-2026-07-20.md (added 2026-07-20, extended 2026-07-20 with cross-fork misroute detection probes) — only if the user's input IS a patch bundle uploaded as a Slack attachment or ~/Downloads file; git apply --check cwd pitfall, cross-fork misroute detection probes (author email + base SHA + source-repo github existence + every diff --git path present in target repo HEAD), /super redirect, /aar semantic mismatch, claude -p rate-limit fallback, right ack shape on misroute, cleanup recipe (br close + locked-worktree git worktree remove --force --force + .git/rebase-apply/ wipe).
learn + skillify + harness-engineering + using-git-worktrees (added 2026-07-14 — load all four in parallel when classification returns learn + skillify + harness closeout)
Reference map — when each reference applies
references/ao-spawn-internal-error-pivot-2026-07-12.md — AO ao spawn returns INTERNAL_ERROR despite healthy daemon. Decision matrix for pivot-to-inline vs surface blocker. Verified on PR #8337.
references/stale-pr-branch-rebase-conflict-2026-07-14.md — companion to the above for the CONFLICTING-PR variant: merge vs rebase decision matrix, --theirs/--ours semantics flip, set -e pitfall, --force-with-lease push to original PR branch, GitHub auto-merge cycle, single-gate pool-exhaustion end-state. Verified on PR #8290.
references/no-stop-after-clarify-silence-2026-07-14.md — 60-min clarify silence ≠ stop authorization. Drive to PR-open end-state in the same session. Verified on PRs #328 + #8402.
references/reconstruct-from-prior-session-2026-06-28.md — when the user asks "is X finished?", reconstruct from prior session via session_search + hermes sessions export instead of redoing. Verified on the 2026-06-28 audit-recovery case.
references/learn-skillify-harness-closeout-2026-07-14.md (added 2026-07-14) — when the user types /learn + /skillify + /harness + /newb in the same turn, the FULL closeout loop is required: learn → skillify → harness-engineering → fresh-worktree verify. Verified on PR #329 clean replay + SOUL.md ## COMMIT: never-push-onto-someone-elses-pr-head + pr-cleanup-replay Phase -1 + 3 new contract tests.
references/pr-description-validator-gate6b-2026-07-15.md (added 2026-07-15) — $GITHUB_REPOSITORY Gate 6b PR description validator (pr_description_gate.py) + Evidence Gate Check 7 freshness policy + Skeptic Gate 7 (NOT LIVE in this repo). Pull-the-validator-locally recipe + LLM marker list + behavioral-file regex carve-out + Path A (fresh capture) vs Path B (truthful acceptance + MERGE APPROVED). Verified on PR #8406.
references/wrong-target-removal-stop-X-from-Y-2026-07-20.md (added 2026-07-20) — Phase 0.5 disambiguation for "stop X from Y" requests where the named target X is the wrong target (downstream consumer). Trace symptom provenance to actual source before removing the literal noun. Companion to harness-postmortem Phase 0 working class wrong-target-removal-on-stop-X-from-Y. Verified on Slack thread C0AJ3SD5C79/p1784344760053389.
references/prompt-contract-cr-scope-broadening-2026-07-21.md (added 2026-07-21) — when CodeRabbit flags static-content blockers (named-entity leaks, formula/example mismatch, hidden-state leaks, contradictory clause pairs, malformed placeholders) wider than the existing static test enforces, the canonical recipe is: (1) broaden the existing static test's forbidden list FIRST, (2) run it locally to enumerate the leak surface before any prompt edit, (3) add a class with contract-pinning assertions for the OTHER 4 blocker classes (exact formula substring, exact phrasing, exact no-leakage of artifacts), (4) commit + push as one PR ref. Companion to for prompt-edit PRs specifically. Verified on $GITHUB_REPOSITORY PR #8488 V3.21 (head f9f269a685).
Worked example — the 2026-06-19 incident
User said: "Look at the last week of slack threads with work that started but didn't finish. … Is there some way we can /skillify Hermes to be more hands off? I want it to fully drive everything to a conclusion like a final /green PR … correct but misinterpret is fine but stopping halfway is not."
Phase 0 classified: meta / about-Hermes (skillify skill).
Phase 1: /fs was unnecessary — the request itself is a skillify task, not a feature implementation.
Phase 2: Inline execution (single-session skillify pass). No dispatch needed.
Phase 3: Built the skill, ran the 10-item checklist, deployed, verified all artifacts in the same turn.
Phase 4: Final reply with the 10-item re-audit (counts of files, line numbers, deploy SHA) — no follow-up question. The user's rule is satisfied: the work landed, the skill is reachable from the resolver, the SOUL.md commit fires it automatically on the next goal-shaped message.
Worked example — the 2026-07-14 PR #8290 incident
User asked for fullrun on the Slack digest next-actions. The digest flagged: "Daily Level Up (4/8) + Dice Audit (1/2) tests FAILED on 2026-07-14." PR-topology pre-flight identified PR #8290 (feat/daily-level-up-2026-07-08, head f81c860e0) as the canonical fix — but mergeable=CONFLICTING.
Phase 0 classified: PR-fix on existing branch, scope = conflict resolution + push.
Phase 2: ao spawn --claim-pr 8290 --no-takeover --prompt "..." returned Internal server error (INTERNAL_ERROR) despite ao doctor showing the daemon healthy. Per references/ao-spawn-internal-error-pivot-2026-07-12.md, pivoted to inline execution.
Phase 3: Used the verified recipe from references/stale-pr-branch-rebase-conflict-2026-07-14.md. git fetch origin main pull/8290/head:pr8290; git checkout -B fix/pr8290-rebase origin/main; git merge pr8290 --noff --no-edit. One conflict in $PROJECT_ROOT/tests/test_prompt_embedding_store.py — caused by PR #8394 narrowing the deploy-probe contract test after #8290's branch was created. Read conflict markers (grep -nE "<<<<<<< |=======|>>>>>>>"), took HEAD (main, post-#8381 narrower contract — newer wins). git add + git commit --no-edit (dropped set -e after the first attempt aborted the chain before the commit). Pushed with --force-with-lease origin fix/pr8290-rebase:feat/daily-level-up-2026-07-08. PR went from CONFLICTING → MERGEABLE+CLEAN.
Phase 4: Final reply with PR URL, new head SHA 3cbbaf6b7c → GitHub auto-merge cycle produced aff95f87e3, all Gates 1-6 PASS, single remaining gate is the documented pool-exhaustion pattern (verified on 6+ other PRs). Posted PR comment with full resolution notes. Created one-time follow-up cron 13d12449f1cf per one-time-status-cron-after-every-task. User owes MERGE APPROVED.
Worked example — the 2026-07-14 PR #329 + learn/skillify/harness closeout incident
User typed (Slack thread C09GRLXF9GR/p1784083166): "this isnt a clean PR from origin main why do you kee pscrewiing this up? /learn and /skillify and dont we ahvr a fresh worktree skill or instrucitons to use /newb? lets run /harness and then fix it".
Phase 0 classified: learn + skillify + harness closeout (four actions named in the same message — load all four skills, do NOT pick one).
Phase 2: Inline execution. PR-topology pre-flight: PR #321 in jleechanorg/claude-commands is open with head 1a43307a0 (the agent earlier commits) sitting on top of the legitimate head 286311a97 (+670k/-24k / 3001 files baseline). The earlier git push origin HEAD:refs/heads/fix/real-claude-team-tmux polluted the PR. Recovery via pr-cleanup-replay Strategy B (extract file diff). Opened PR #329 with 1 commit / 5 files / +427/-691 branched from origin/main. Closed PR #321 with reference. Hit the gitleaks pre-push hook bypass (4259 false-positive leaks from pre-existing history) — applied git -c core.hooksPath= push -u origin fix/sidekick-5min-checkpoint for the one-shot push.
Phase 3 (closeout loop):
/learn: wrote ~/.claude/projects/-Users-$USER-claude-commands/memory/feedback_2026-07-14_feedback-pr-push-onto-someone-elses-pr-head-pollution.md + appended to ~/roadmap/learnings-2026-07.md + created + closed bead $USER-4a9.
/skillify: extended pr-cleanup-replay with Phase -1 (Prevention), updated RESOLVER.md heading with prevention triggers, added 3 new contract tests (5 → 8 passing). Added references/gitleaks-pre-push-hook-bypass.md (this skill new reference file).
/harness: added ## COMMIT: never-push-onto-someone-elses-pr-head to SOUL.md with Trigger/Action/Why/Files; created docs/agent/anti-patterns.md overlay in the worktree (untracked, for a future PR).