claude-harness-canary
Skill84 starsChanged 8 days agoMITView on GitHub
Run the minimum current-Claude compatibility canary for hermitd by driving real interactive Claude Code instances in tmux with --plugin-dir. Use before a core release or after a Claude Code upgrade to verify cached model switching, unattended prompt safety, and native Monitor/Cron routine lifecycle. Core only; not application testing or CI.
- Deletes or force-pushes
1 other project uses a version of this.
Adopted by
Most recently taken up by
- gtapps/hermitdword for word, first seen Nov 7, 2026
Other versions1 project uses a version of this.
Word for word · 1
- gtapps/hermitd★ 84100% same
Edited · 0
None found.
Wrote their own version · 0
None found.
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository29 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- bump-core-reqSkill
- commitSkill
- docs-driftSkill
- downstream-impactSkill
- fleet-releaseSkill
- pipeline-digestSkill
- pre-release-reviewSkill
- pricing-refreshSkill
- releaseSkill
- release-statusSkill
- routine-test-pingSkill
- stale-proposalsSkill
- tackle-issueSkill
- test-runSkill
- bump-core-reqSkill
- commitSkill
- docs-driftSkill
- downstream-impactSkill
- fleet-releaseSkill
- pipeline-digestSkill
- pre-release-reviewSkill
- pricing-refreshSkill
- releaseSkill
- release-statusSkill
- routine-test-pingSkill
- stale-proposalsSkill
- test-runSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
44 rules. About 0 are common, 22 are distinctive, and 22 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈22
Distinctive · estimate
This author's own choices. Worth a look.
22
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 8
Run all three groups serially against the currently installed Claude Code.
- Very specific: line 10
Keep this canary core-only, current-Claude-only, interactive-tmux-only, and loaded through --plugin-dir.
- Distinctive: line 25
If any property is absent, stop before preflight and report:
- Distinctive: line 29
Do not recreate generic tmux lifecycle rules locally or hand-roll a fallback.
- Distinctive: line 33
Run from the repository root.
- Very specific: line 90
Use the core state-templates/SHELL.md.template as sessions/SHELL.md.
- Distinctive: line 96
Before each formal group launch:
- Very specific: line 99
Read the default profile's ~/.claude.json and require the exact .projects["<absolute-group-project>"] entry to be absent.
- Very specific: line 102
After readiness and before the first action, require state/last-operator-action.json to exist with a parseable at timestamp.
- Very specific: line 114
Write state/runtime.json with the exact group tmux name, runtime_mode: "tmux", and a running status.
- Very specific: line 116
Never use --continue or --resume.
- Very specific: line 142
no required CronList, Monitor, TaskStop, CronCreate, or CronDelete call.
- Distinctive: line 143
no requested output nonce.
- Distinctive: line 145
Before retrying, tear down every exact native resource harvested from that attempt and kill only its named tmux session.
- Very specific: line 147
Never retry a directly observed compatibility failure, authentication/tmux failure, unexpected or missing required /model dialog, hook …
- Very specific: line 153
Use Haiku with acceptEdits, the full core hook manifest, and AGENT_HOOK_PROFILE=minimal.
- Distinctive: line 166
Do not hand-write marker JSON and do not add a channel fixture.
- Distinctive: line 171
Verify the completed assistant transcript entry is Haiku-class.
- Very specific: line 172
Call writePendingCommand() and verify it returned true and the marker exists.
- Distinctive: line 193
no unrelated modal or prompt received Enter.
- Distinctive: line 201
If the switch succeeds directly without the required dialog, do not retry.
- Very specific: line 209
Run both assertions in one fresh Sonnet session (--model sonnet).
- Very specific: line 225
Do not add allowed_users. Do not create state/channel-health.json.
- Distinctive: line 231
Wait for the native permission dialog.
- Very specific: line 232
Read the session's own entry in the Claude Code session registry (~/.claude/sessions/<pid>.json for the claude process in the group's tmux …
- Distinctive: line 234
Verify the target does not exist.
- Distinctive: line 242
Wait for its final assistant response and output-only nonce.
- Distinctive: line 250
no native Ask widget rendered.
- Very specific: line 256
If Claude never attempts AskUserQuestion, cleanly retry the whole group once in a fresh session.
- Very specific: line 262
Use a fresh Sonnet session (--model sonnet) in acceptEdits, with AGENT_HOOK_PROFILE=standard, no channel, no external integration, and …
- Distinctive: line 270
Use the live deterministic settings operation before launch:
- Distinctive: line 277
Do not hand-copy the allow-list.
- Very specific: line 285
If the list is nonempty, perform no scheduler mutation.
- Very specific: line 287
Do not use TaskList as a substitute for Monitor inventory.
- Distinctive: line 295
Wait with one bounded action wait.
- Very specific: line 322
Never invoke hermit-routines stop --all.
- Distinctive: line 328
Verify every harvested Cron ID is absent and each native deletion succeeded.
- Very specific: line 332
If Claude omits a required native call without an explicit tool failure, clean exact resources and retry once in a fresh group.
- Very specific: line 336
Do not run /compact in the fixed canary.
- Very specific: line 338
Run a separate one-off /compact tmux probe only when the release changes /compact delivery, PreCompact, compact SessionStart, or …
- Distinctive: line 340
Treat other Claude-owned assumptions the same way.
- Distinctive: line 346
Stop or delete only native resources created by that group and identified by exact returned IDs.
- Distinctive: line 374
Every INCONCLUSIVE row must include:
- Very specific: line 378
Do not present INCONCLUSIVE as a compatibility failure.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Claude Harness Canary
Run all three groups serially against the currently installed Claude Code. Take no arguments and do not offer group selectors. Target 5–8 minutes, but preserve bounded waits and evidence over that estimate.
Keep this canary core-only, current-Claude-only, interactive-tmux-only, and loaded through --plugin-dir. Never add it to CI, release commands, or $pre-release-review.
Shared probe prerequisite
Reuse the installed $probe lifecycle. Before creating canary state, read its active SKILL.md and require all of these properties:
- real interactive Claude in tmux, never
claude -p, Codex, or another PTY; - no interactive
--no-session-persistence; tmux pipe-panestarts before any input;- one bounded readiness phase that detects the trust dialog, accepts it only when present, handles only explicitly recognized startup onboarding, and waits for the normal prompt;
- a unique output-only completion nonce whose complete value is never contiguous in the injected prompt;
- completion accepted only from assistant output after the requested evidence, never prompt echo;
- one bounded wait per canary action;
- teardown with
rm -ronly after validating the exact scratch path.
If any property is absent, stop before preflight and report:
PREREQUISITE BLOCKED — update the local $probe protocol
Do not recreate generic tmux lifecycle rules locally or hand-roll a fallback. Extend the probe lifecycle only for the multi-action assertions below.
Preflight
Run from the repository root.
- Resolve the absolute core path at
plugins/hermitd. Record bounded output from:
claude --version claude --help tmux -V claude auth status --json- Require current help to expose
--plugin-dir,--model,--permission-mode, and--session-id. - Require authentication through the default profile. Do not set
CLAUDE_CONFIG_DIR. Require these core files:
.claude-plugin/plugin.jsonhooks/hooks.jsonscripts/lib/harness-command.tsscripts/stop-pipeline.tsscripts/hermitd-watchdog.tsscripts/ask-gate.tsscripts/apply-settings.tsscripts/routines.tsscripts/routine-monitor.shskills/hermit-routines/SKILL.md
- List tmux sessions and require no session whose exact name begins
claude-harness-canary-. Do not kill an unknown prior session; report the prerequisite collision. - Inspect help for strict external-MCP isolation. When supported, use an empty file containing
{"mcpServers":{}}with--mcp-configand--strict-mcp-config. If--no-chromeis supported, use it too: strict MCP config does not disable Claude's in-process Chrome integration. If the installed version does not support the strict MCP flags, record that limitation and continue with--setting-sources project,local; do not invent flags. - Apply the shared probe's one-time fullscreen-renderer onboarding normalization before the canary run. Do not attempt to detect that dialog by grepping raw
pipe-paneoutput; current renderer terminal deltas scramble the visible wording.
Classify missing tmux, failed authentication, a stale canary session, or a deficient $probe as PREREQUISITE BLOCKED. Classify missing/rejected --plugin-dir as compatibility FAIL. Classify missing core files as implementation/setup FAIL.
Run workspace
Generate a lowercase alphanumeric run ID and create exactly:
/tmp/claude-harness-canary-<run-id>/
Resolve and record the absolute path. Create one subdirectory per group and one replacement subdirectory only if that group uses its permitted retry. Give every process a unique tmux name and a UUID produced by uuidgen or an equivalent UUID generator.
For every group project, create:
<group>/
├── .claude/
│ └── settings.local.json
├── .hermit/
│ ├── config.json
│ ├── sessions/SHELL.md
│ └── state/
│ ├── runtime.json
│ └── micro-proposals.json
├── empty-mcp.json
└── evidence/Use the core state-templates/SHELL.md.template as sessions/SHELL.md. Initialize micro-proposals.json to the schema currently consumed by core; use an empty object/array only if the live reader accepts it. Keep every fixture minimal.
Workspace trust
Plugin hooks are disabled until Claude trusts the exact project directory. A normal prompt is not proof of trust: Claude Code can reach it for a new /tmp project while recording hasTrustDialogAccepted: false and silently skipping every hook (observed on 2.1.220, 2026-07-27; still true on 2.1.258).
Before each formal group launch:
- Resolve the group project and require it to be a direct child of the recorded run root.
- Read the default profile's
~/.claude.jsonand require the exact.projects["<absolute-group-project>"]entry to be absent. A pre-existing entry means the supposedly fresh group is not isolated; stop rather than overwriting it. - Because the canary created and controls every byte in this exact directory, atomically add only that exact project entry with
{"hasTrustDialogAccepted":true}. Preserve every unrelated key and the file's mode. Use an optimistic read/verify/write; if the profile file changes concurrently, re-read and retry rather than replacing a newer file. Never trust the run root,/tmp, the repository, a parent directory, or any path the canary did not create. - Re-read the profile and require the exact flag to be
truebefore launching Claude. Record the trust preparation without copying unrelated profile state into evidence. - After readiness and before the first action, require
state/last-operator-action.jsonto exist with a parseableattimestamp. This is the deterministic SessionStart-hook sentinel. A normal prompt without this file isFAIL — workspace trust/hook readiness not established; do not continue the group.
The trust entry is temporary canary state. During cleanup, after the exact group's Claude process is gone, atomically remove only its exact .projects[...] entry if it was absent before preparation. Preserve all unrelated profile state and handle concurrent writes with the same optimistic retry. Do this on PASS, FAIL, and INCONCLUSIVE; removing the project entry does not remove its transcript.
Set in every inner process:
AGENT_DIR=<absolute-group-project>/.hermitCLAUDE_PROJECT_DIR=<absolute-group-project>- a group-specific
AGENT_HOOK_PROFILE HERMIT_MANAGEDonly where the group requires itCLAUDE_CODE_TASK_LIST_IDunset withenv -u CLAUDE_CODE_TASK_LIST_ID
Write state/runtime.json with the exact group tmux name, runtime_mode: "tmux", and a running status. Do not include transition, shutdown_requested_at, or shutdown_completed_at.
Never use --continue or --resume. Launch from the group project with the current equivalent of:
env -u CLAUDE_CODE_TASK_LIST_ID \
AGENT_DIR="<absolute-hermit-dir>" \
CLAUDE_PROJECT_DIR="<absolute-project-dir>" \
AGENT_HOOK_PROFILE="<profile>" \
claude --model "<group-model>" \
--permission-mode "<mode>" \
--session-id "<uuid>" \
--setting-sources project,local \
--plugin-dir "<absolute-core-path>" \
<supported-empty-mcp-and-no-chrome-flags>Start each process in a 220×50 tmux pane behind the shared probe’s one-shot FIFO launch gate. Attach tmux pipe-pane while the gate is closed, then release exec claude once so no startup screen can race logging. Use the shared probe readiness phase: one bounded 60-second wait that handles an optional trust dialog and returns only at the normal prompt. The profile flag and SessionStart sentinel above are still mandatory; renderer readiness alone is insufficient.
For each action, generate a new nonce split into two prompt fragments. Wait for the concatenated output-only nonce with one bounded timeout/tail -F action wait, capped at 60 seconds for this canary. Record the log offset before the action so earlier output cannot satisfy the wait. Verify every match belongs to a Claude assistant response block and follows the action evidence. If 60 seconds expires, stop the passive wait and immediately take one tmux capture-pane -p -t <session> for diagnosis; inspect the known transcript rather than waiting for the generic probe's ten-minute ceiling.
Locate transcripts only by the known session UUID under the default Claude profile. Never inspect or record unrelated transcripts.
Retry policy
Retry a group once only when the model did not exercise the requested action, for example:
- no
AskUserQuestioncall; - no required
CronList,Monitor,TaskStop,CronCreate, orCronDeletecall; - no requested output nonce.
Before retrying, tear down every exact native resource harvested from that attempt and kill only its named tmux session. Use a fresh project, tmux name, and Claude UUID.
Never retry a directly observed compatibility failure, authentication/tmux failure, unexpected or missing required /model dialog, hook contradiction, nonempty scheduler baseline, explicit native tool failure, or cleanup failure.
Group 1 — Cached /model switch
Setup
Use Haiku with acceptEdits, the full core hook manifest, and AGENT_HOOK_PROFILE=minimal. Set HERMIT_MANAGED to empty. Seed a minimal interactive config with no channels, heartbeat, routines, transitions, or shutdown stamps. Point runtime.json at this group’s exact tmux session.
This journey begins at state/pending-harness-command.json. Stage it outside the inner Claude process by importing and calling the core module’s exported writePendingCommand() with:
{
"command": "/model",
"arg": "sonnet",
"by": "claude-harness-canary",
"requested_at": "<current ISO timestamp>"
}Do not hand-write marker JSON and do not add a channel fixture.
Procedure
- Send a small context-establishing prompt and wait for its output-only nonce.
- Verify the completed assistant transcript entry is Haiku-class.
- Call
writePendingCommand()and verify it returned true and the marker exists. - Record the pipe log offset. Send a short trigger prompt and wait for the completed turn. Its real Stop hook must drain the marker.
In output after the recorded offset, require the current cached-context model-switch dialog anchors implemented by core:
Switch model?This conversation is cached for the current model.Yes, switch toNo, go back
- Allow only the real Stop pipeline to confirm it. Send no canary Enter. Wait for the dialog to clear and the normal prompt to return.
- Send a post-switch prompt with a new output-only nonce.
- Locate the transcript by UUID and inspect the newest assistant entry’s
message.model.
Verdict
PASS only when:
- pane and Stop-hook evidence show one submission event for
/model sonnet; - the marker was removed only after successful command submission;
- the required cached-context dialog appeared after the trigger;
- it cleared without canary input;
- no unrelated modal or prompt received Enter;
- the post-switch turn completed;
- the newest assistant model is Sonnet-class, not Haiku.
Count semantic submission events, not incidental hook log repetitions of the command text.
FAIL when the dialog remains open, confirmation is not sent, an unrelated modal receives Enter, the command is submitted twice, the marker disappears without submission, or the post-switch model remains Haiku.
If the switch succeeds directly without the required dialog, do not retry. Report:
INCONCLUSIVE — switch worked, cached-confirmation contract not exercised
and add Not exercised — needs manual review.
Group 2 — Native modal and Ask gate
Run both assertions in one fresh Sonnet session (--model sonnet). Use Claude’s native manual approval mode (--permission-mode manual). Set HERMIT_MANAGED=1, always_on: true, ask_gate: true, and AGENT_HOOK_PROFILE=standard.
Seed exactly one eligible fake channel:
{
"channels": {
"primary": "canary",
"canary": {
"enabled": true,
"default_chat_id": "canary-chat"
}
}
}Do not add allowed_users. Do not create state/channel-health.json. Keep .claude/settings.local.json free of Hermit’s permission allow-list so the first assertion reaches a real native permission dialog.
A. Native permission modal
- Choose a unique harmless target inside the group project.
- Prompt Claude to call
Writeonce to create it. - Wait for the native permission dialog.
- Read the session's own entry in the Claude Code session registry (
~/.claude/sessions/<pid>.jsonfor the claude process in the group's tmux pane) while the dialog is open, and record itsstatusandwaitingFor. - Send Escape once, wait for the normal prompt, and send a harmless nonce prompt to prove the session remains usable.
- Verify the target does not exist.
PASS requires a real native permission modal, registry status waiting while it is open, Escape cancellation, no target file, and a usable session.
B. Ask-gate regression
- Record the transcript and log offsets.
- Prompt Claude to invoke
AskUserQuestionexactly once with three fixed harmless options. Require the call rather than a prose-only answer. - Wait for its final assistant response and output-only nonce.
- Inspect only this action’s transcript entries and log slice.
PASS requires:
- exactly one
AskUserQuestiontool-use attempt; - the real
ask-gate.tsPreToolUse hook denied it with exit 2; - the hook’s redirect reason reached Claude;
- no native Ask widget rendered;
- Claude did not retry
AskUserQuestion; - the session returned to a usable prompt.
Claude may fall back to prose. Do not require a channel reply or micro-proposal.
If Claude never attempts AskUserQuestion, cleanly retry the whole group once in a fresh session. If it declines again, report INCONCLUSIVE and Not exercised — needs manual review.
Group 3 — Routine scheduler lifecycle
Setup
Use a fresh Sonnet session (--model sonnet) in acceptEdits, with AGENT_HOOK_PROFILE=standard, no channel, no external integration, and CLAUDE_CODE_TASK_LIST_ID unset.
Seed UTC config with:
- one enabled
heartbeat-restartanchor; - one enabled routine whose ID includes the run ID and whose harmless skill is an existing core read-only skill;
- schedules on the first day of a month at least five months ahead, separated by five minutes, so neither can fire during this run.
Use the live deterministic settings operation before launch:
bun "<core>/scripts/apply-settings.ts" \
"<project>/.claude/settings.local.json" permissions-syncDo not hand-copy the allow-list.
Isolated baseline
- First prompt Claude to call
CronListand make no mutation. PermitToolSearchonly when this Claude version requires it to expose the deferredCronListtool; forbid every other tool and scheduler mutation. - Inspect the transcript call and result.
- Require an empty list.
If the list is nonempty, perform no scheduler mutation. Report FAIL — scheduler baseline was not isolated, preserve evidence, and continue only with tmux cleanup.
Do not use TaskList as a substitute for Monitor inventory; it is a checklist surface. The routine skill's own exact-description TaskList orphan sweep remains permitted.
Load
Send:
/hermitd:hermit-routines load
Wait with one bounded action wait. Harvest every native creation result from the known transcript:
- Monitor
taskId; - every
CronCreateID; - selected runtime branch.
Accept either supported branch.
Monitor branch PASS evidence:
- Monitor returns a task ID;
- description is exactly
routine-monitor; state/routine-monitor.runtime.jsonrecordsmode: "monitor"and that task ID;state/routine-monitor-liveness.jsonappears;- the
heartbeat-restartanchor is created throughCronCreate.
Cron fallback branch PASS evidence:
- Monitor is explicitly unavailable or fails liveness verification;
- the workflow selects
croncreate-fallback; - each expected routine receives a
CronCreateID; - runtime state records
mode: "croncreate-fallback".
A correctly selected, working fallback is PASS.
Exact-ID teardown
Never invoke hermit-routines stop --all. Never delete by prompt pattern, routine prefix, description, or an ID not harvested from this run.
- Send one teardown prompt containing only the exact harvested IDs.
- Require
TaskStoponly for the Monitor task ID returned by this run. - Require
CronDeleteonly for Cron IDs returned by this run. - Require one final
CronList. - Verify every harvested Cron ID is absent and each native deletion succeeded.
- Terminate this group’s Claude process, which independently ends its session Monitor and prevents session-scoped crons from firing.
- Preserve teardown transcript evidence before scratch cleanup.
If Claude omits a required native call without an explicit tool failure, clean exact resources and retry once in a fresh group. Never retry an explicit tool failure or any cleanup failure. Cleanup failure is FAIL, not INCONCLUSIVE.
Optional release-specific probe
Do not run /compact in the fixed canary.
Run a separate one-off /compact tmux probe only when the release changes /compact delivery, PreCompact, compact SessionStart, or compaction-context reinjection. Do not add it to this fixed skill until a cheap, reliable fixture guarantees compactable context.
Treat other Claude-owned assumptions the same way. Promote one into the fixed canary only when repeated failure would stall Hermit, bypass a safety boundary, or disable always-on operation.
Cleanup
For every outcome:
- Stop or delete only native resources created by that group and identified by exact returned IDs.
- Kill only each group’s recorded tmux session.
- Re-resolve the run root and require exact equality with the recorded
/tmp/claude-harness-canary-<run-id>path. - On an all-PASS run, remove that exact root with
rm -r. - On FAIL or INCONCLUSIVE, preserve evidence after native and tmux cleanup and report its exact path.
Never use rm -rf.
Report
Record the Claude version, model and permission mode per group, tmux and Claude session identifiers, bounded pane/transcript evidence, retry use, and exact cleanup IDs/results. Never record credentials, account identity, unrelated transcripts, or real Hermit state.
Use:
# Claude Harness Canary — <Claude version>
| Group | Verdict | Duration | Evidence |
|-------|---------|----------|----------|
| Cached /model switch | PASS/FAIL/INCONCLUSIVE | ... | ... |
| Prompt safety | PASS/FAIL/INCONCLUSIVE | ... | ... |
| Routine scheduling | PASS/FAIL/INCONCLUSIVE | ... | ... |
Overall: PASS | FAIL | INCONCLUSIVEPASS means every required assertion and cleanup check passed. FAIL means a required contract was directly contradicted. INCONCLUSIVE means the contract was not exercised after the permitted noncompliance retry. Only PASS is green.
Every INCONCLUSIVE row must include:
Not exercised — needs manual review
Do not present INCONCLUSIVE as a compatibility failure. If preflight never starts, report PREREQUISITE BLOCKED instead of the table.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

