Hook Development for Claude Code Plugins
anthropics/claude-plugins-official
Explains how to write Claude Code plugin hooks, both prompt-based checks and bash commands, for events such as PreToolUse, Stop and SessionStart.
Understand and respond to the Interlinked PreToolUse guard — the local daemon that BLOCKS dangerous tool calls before they run.
The automated check flagged lines worth reading first. See the safety section below.
$ npx skills add QuentinCody/interlinked-cli --skill interlinked-harness -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install QuentinCody/interlinked-cli interlinked-harness --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/QuentinCody/interlinked-cli.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills/interlinked-harness .claude/skills/interlinked-harness && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "interlinked-harness" agent skill from https://github.com/QuentinCody/interlinked-cli/tree/main/skills/interlinked-harness into .claude/skills/interlinked-harness/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "interlinked-harness", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/QuentinCody/interlinked-cli/tree/main/skills/interlinked-harnessType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add QuentinCody/interlinked-cli --skill interlinked-harness -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install QuentinCody/interlinked-cli interlinked-harness --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/QuentinCody/interlinked-cli.git skills-src && mkdir -p .agents/skills && cp -r skills-src/skills/interlinked-harness .agents/skills/interlinked-harness && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "interlinked-harness" agent skill from https://github.com/QuentinCody/interlinked-cli/tree/main/skills/interlinked-harness into .agents/skills/interlinked-harness/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "interlinked-harness", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add QuentinCody/interlinked-cli --skill interlinked-harness -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install QuentinCody/interlinked-cli interlinked-harness --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/QuentinCody/interlinked-cli.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/skills/interlinked-harness .cursor/skills/interlinked-harness && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "interlinked-harness" agent skill from https://github.com/QuentinCody/interlinked-cli/tree/main/skills/interlinked-harness into .cursor/skills/interlinked-harness/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "interlinked-harness", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/QuentinCody/interlinked-cli.git --path skills/interlinked-harness--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add QuentinCody/interlinked-cli --skill interlinked-harness -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install QuentinCody/interlinked-cli interlinked-harness --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/QuentinCody/interlinked-cli.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/skills/interlinked-harness .gemini/skills/interlinked-harness && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "interlinked-harness" agent skill from https://github.com/QuentinCody/interlinked-cli/tree/main/skills/interlinked-harness into .gemini/skills/interlinked-harness/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "interlinked-harness", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install QuentinCody/interlinked-cli interlinked-harnessInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add QuentinCody/interlinked-cli --skill interlinked-harness -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/QuentinCody/interlinked-cli.git skills-src && mkdir -p .github/skills && cp -r skills-src/skills/interlinked-harness .github/skills/interlinked-harness && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "interlinked-harness" agent skill from https://github.com/QuentinCody/interlinked-cli/tree/main/skills/interlinked-harness into .github/skills/interlinked-harness/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "interlinked-harness", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add QuentinCody/interlinked-cli --skill interlinked-harness -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install QuentinCody/interlinked-cli interlinked-harness --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/QuentinCody/interlinked-cli.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/skills/interlinked-harness .opencode/skills/interlinked-harness && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "interlinked-harness" agent skill from https://github.com/QuentinCody/interlinked-cli/tree/main/skills/interlinked-harness into .opencode/skills/interlinked-harness/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "interlinked-harness", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
interlinked-harnessUnderstand and respond to the Interlinked PreToolUse guard — the local daemon that BLOCKS dangerous tool calls before they run.
Interlinked Harness is an agent skill from QuentinCody/interlinked-cli. Understand and respond to the Interlinked PreToolUse guard — the local daemon that BLOCKS dangerous tool calls before they run. Load this when a Bash command or file edit was refused with "BLOCKED: … Suggestion: …", when you see an [interlinked:check-id] warning tagged [proven] or [heuristic], when a destructive command / force-push / protected-file / secret / repo-confinement rule fired, when a grep was answered by the index, or when you need to know how to legitimately suppress a false positive or disable a…
Its SKILL.md is about 11k tokens, which your agent loads only when the skill is triggered. The skill folder holds 2 other files (for example `agents/openai.yaml`).
It works with Bash. The repository describes itself as: The harness for your harness. Local hooks, taste enforcement, and developer observability for AI coding agents (Claude Code, Codex, Cursor, Copilot CLI). The licence is MIT.
5 steps, taken from the first numbered list in SKILL.md.
Read from SKILL.md and the folder at commit 31b5259. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
gitcurlnpmdockerkubectlterraformpuluminpxFrom the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md. Its commands use git, curl, npm, docker, kubectl and npx, which can reach the network depending on how they are called.
From URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Interlinked Harness loads about 11k tokens when it runs. Until then it costs about 167 tokens; SKILL.md has 5,416 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found patterns that need a careful read before installing.
-rf /`, `rm -rf *`, `rm node_modules`, `sudo rm …`, `dd`, `mkfs`, `shred`, `chmod 777` | `rm -rf /tmp/x`, `rm -rf dist/(`env \| curl …`, `printenv \| nc …`), `.npmrc`/`.yarnrc` writes, `nohup curl … &`, `crontab -e`, `systemctl enable`, wr| **Sensitive-file read** | `Read` of `.env`, `credentials.json`, `service-account*.json`, `*.pem`/`*.key` | `.env.examp`.env`, while collapsing bulk ignored directory trees) before the call and attaches the observedAutomated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from QuentinCody/interlinked-cli at commit 31b5259, republished under its MIT licence (© QuentinCody). 5,416 words, ~10,547 tokens.
.claude/skills/interlinked-harness/SKILL.md (or your agent's skills folder). This skill also uses 1 other file; get the full folder from GitHub.The prediction phase includes a local JS/TS if-ownership oracle, independent of
Supermodel and graph_prediction.enabled. Its default mode is enforced;
harness.guard_prediction.mode in shared .interlinked/config.json accepts
enforced, shadow (record/reveal without refusing), or off.
Before intentionally changing which if guards an existing return or throw,
declare the expected ownership change. The raw before/after AST comparison runs
before content checks; do not brace or format away an unexpected change.
Unpredicted changes block as protocol mismatches, not proven bugs. Correct the
proposal, or reconcile the exact revealed change with a rationale and behavioral
validation. A matching prediction does not prove correctness.
Receipts are JSON at .interlinked/predictions/guards/<sha256(session)>/<id>.json.
Fields: version: 1, session, id, file (canonical repository-relative path),
beforeSha256, afterSha256, nonce (nonempty declaration identifier), and
changes: an array of {owner, statement, before, after}. Statement and condition
strings use parser token spellings joined by spaces. Each guard is
{condition, branch: "then" | "else"}; arrays list outer to inner guards. The ID is
SHA-256 of JSON.stringify([1, file, beforeSha256, afterSha256]), with UTF-8 hashes
of the exact source bytes. Use the native session identity printed in a reveal.
The reveal includes the measured fields and receipt path. After a reveal, include
reconcile: id and a nonempty rationale; a late prediction alone cannot pass.
Identical retries retain their declaration; changed bytes, file or session require
a matching receipt. Never use a wildcard acknowledgment.
The append-only predictions/guard-events.jsonl retains reveal, predicted, and
reconciled evidence; acceptance never erases a surprise. Dry runs do not persist
events. Invalid or inaccessible protocol state cannot authorize a changed proposal.
This is local intent bookkeeping, not an adversarial security boundary.
The initial oracle handles unique existing statements in identifiable function
scopes, plus unchanged complete function bodies. Changed ambiguous/anonymous
scopes and changed statement text produce partial/NOT CHECKED evidence. It does not
establish loop/switch/exception dominance, semantic predicate equivalence, deleted
statements, cross-file moves, or arbitrary shell effects. Native cold fallback runs
the same oracle; a standalone generated hook without the daemon reports NOT CHECKED
because it has no AST runtime. Restore the daemon or use interlinked-hook for
that check. Single-line guards remain valid code; brace style is a separate policy.
Claude's demonstrated native PostToolBatch boundary coalesces TypeScript checks for
declared single-file edits. The final tree is checked before the next model request;
security guards and hard complexity caps remain per edit. Final compiler findings are
repair context, with unresolved work enforced at Stop/commit. See interlinked-verify
for fallback and recovery behavior. Merely using multiple Edit calls is not evidence
of a retry: correlate their emission time and assistant message with hook feedback.
Repeated pre-edit content advice, test-evidence limitations, and unchanged coverage gaps are deduplicated for model delivery. Full observations remain in local evidence. Changing coverage counts alone do not repeat the same instruction; a recovered gap that recurs or a different unavailable-check reason is announced again.
Claude and Codex Stop/SubagentStop advisories remain operator diagnostics on stderr. Only explicit blocking repair decisions request another model turn. Source guidance is sent during editing. File-size advice and per-function gates share native edit/patch content projection; unreadable or invalid updates stay unmeasured. Shell effects remain post-execution evidence unless a supported pre-write path is used.
PostToolUse classifies recognized untracked Python environments, installed Node dependency trees and named harness outputs before authored-source checks. Explicit edits, tracked files, protected policy and unsafe/symlink paths do not receive this exemption. Security scope stays separate; excluded files are not passes and runtime-input fingerprints are not narrowed. The same source role separates recovery batches. Repeated unavailable-check diagnostics are deduplicated for delivery; raw findings and unresolved evidence remain. Retry when prerequisites change, not merely because the same diagnostic repeats. Missing evidence cannot yield “all clean.”
For edits outside the guarded repository, both command-based and named external checks (affected tests and dependency audit) skip this repository's runner. Inline content checks remain available. Security findings on excluded dependency files retain normal severity and enforcement; source-quality exclusion is not a security exemption.
The built-in generic rm prior-inspection rule is advisory. A recent literal Read event
does not prove inspection of the deletion targets, and clients may expose different read tools.
Inspect the intended targets using the available tools; this heuristic does not require a
particular invocation. Deterministic destructive-command, reservation and protected-file
decisions still apply. Do not infer safe deletion merely from a recent unrelated read.
Guard audit records retain references for repeated warning text. Use interlinked data search --source warning-occurrences to inspect full first/changed messages, hashes and occurrence
counts; data show ID opens their raw evidence. This bounds repetition in the activity mirror
without changing the guard decision or the warning returned by the evaluator. A later
not-reported observation does not prove that the underlying issue was fixed.
Interlinked runs a local daemon that evaluates each pre-execution event the installed runner exposes to its hook surface (PreToolUse). It is default-permit with targeted forbid: it allows everything except known-dangerous shapes, which it refuses with an actionable reason and a safer alternative. It is a fast deterministic guardrail, not a security trust boundary (it's local and bypassable) — so the right instinct when blocked is to take the suggested safe path, not to defeat the pattern.
For the experimental Cowork plugin, first load interlinked-cowork. Its portable guard is a subset of this daemon: exact tool/path denials and the shared destructive command guard. Native crashes/timeouts were observed to fail open; the plugin's launcher mitigates child failure but cannot enforce an absent/provider-killed hook. Host file policies require explicit bridge mapping and matching file snapshots.
BLOCKED: <reason> / Suggestion: <safer alternative>.[interlinked:<check>] warning (tagged [proven] or [heuristic]).git push --force, protected-file, secret, repo-confinement, or
bash-redirect-bypass rule fired.Infinity) step limit.block (tool refused, you see the reason), ask (human confirmation — Claude and
supported Cursor gates can ask natively; interactive Pi calls ctx.ui.confirm; headless Pi,
OpenCode's stable tool gate, Codex PreToolUse, Copilot, and Gemini deny instead;
Claude/Codex PermissionRequest allow/ask abstain so the provider retains authority),
or allow (may still carry non-blocking warnings, or an updated_input rewrite).interlinked harness checks
for the authoritative current inventory. Patterns are ORed over positive entries;
negate:true patterns are exceptions. executed_only masks
quoted/heredoc/comment text, so mentioning rm -rf / in an echo is allowed while the
bare command blocks. Compound commands (&&, ||, ;, |, newline) are decomposed and
each part checked.open(...) checks inspect executable calls and recognize write and append modes;
read-only calls and quoted examples do not establish a file write. Keep malformed tool
input tests at the raw hook-input boundary rather than passing invalid normalized events.head/tail windows of up to 200
lines before rejecting a file over 100 KiB. Small windows pass on large logs; a selected
line over the byte budget still blocks. Signed counts and unfamiliar option combinations
retain the file-size gate. Daemon and generated-hook paths use the same bounded reader.Two unrelated things are called "guard". This skill is the PreToolUse guard (the daemon, operated via
interlinked harness …).interlinked guardis a separate git pre-commit/pre-push hook that checks staged files against file reservations — see interlinked-coordination.
| Category | Blocks (examples) | Allows (examples) |
|---|---|---|
| Destructive fs | rm -rf /, rm -rf *, rm node_modules, sudo rm …, dd, mkfs, shred, chmod 777 | rm -rf /tmp/x, rm -rf dist/, rm -rf .wrangler/cache |
| Process killing | pkill node, killall wrangler, kill -9 1234, kill $(pgrep …), … | xargs kill | pkill -f 'wrangler dev', grep -rn "kill -9" src/ |
| Git | git push --force/-f, git reset --hard, git clean -fd, git checkout -- ., git branch -D, git stash drop/clear, agent-created worktrees (git worktree add; Claude WorktreeCreate) | git push --force-with-lease; git worktree list/remove/prune |
| DB / cloud / IaC / containers | DROP DATABASE/TRUNCATE/DELETE-without-WHERE, docker … prune/rm -f, kubectl delete, terraform destroy, pulumi destroy | — |
| Info-flow / persistence | env exfil (env | curl …, printenv | nc …), .npmrc/.yarnrc writes, nohup curl … &, crontab -e, systemctl enable, writes to /etc/cron.d/, *.service | — |
| Protected files | Read/Write of *.pem/*.key; Write of *.env* only if secrets detected; Delete of CI configs, migrations/**, .gitignore, lockfiles, Dockerfile | .env.example / .sample |
| Sensitive-file read | Read of .env, credentials.json, service-account*.json, *.pem/*.key | .env.example |
| Repo confinement | any Write/Edit whose real (symlink-resolved) target is outside the repo root | paths under the allowlist / session scratchpad |
| Package installs | any un-allowlisted npm/pip/cargo/go/… install; URL/git/tarball specs — see interlinked-supply-chain | allowlisted + exact-pinned |
| Bash-routed write bypass | a > / tee redirect, sed -i / perl -pi / gawk -i inplace / ex / ed in-place edit, patch / working-tree git apply, or a wrapped form (xargs, find -exec, timeout) writing a tracked source file (dodges the content gate) | Write/Edit, interlinked write, or index-only git apply --cached <patch> |
Applier-script execution (builtin-patch-applier) | running an interpreter on a pre-existing throwaway script that writes into repo source | route the edit through Write/Edit; committed codegen belongs in scripts/ |
Bash-edit obligation (bash-edit-obligation) | a bash-channel edit left an INTRODUCED pre_block-class finding on disk; until it is fixed, write-class tool calls to OTHER files are refused (edits to the flagged file and reads stay allowed; the gate re-checks and self-releases) | fix the flagged file first |
| Hand-rolled patch applier | a throwaway script in the scratchpad or scratch/ that calls writeFileSync/appendFileSync/write_text on a path outside its sandbox ("src/…", process.cwd(), ../) — a re-implementation of Edit with the gates removed | probes that only READ repo source; scripts writing beside themselves; committed codegen under scripts/ |
| Content pre_block (introduced-only) | edit that introduces merge-conflict markers, eval(), and other zero-FP checks | pre-existing instances (warn, not block) |
For selected-hunk staging, prepare and review a patch, then run git apply --cached <patch>.
It changes only the index and preserves other working-tree edits. git add -p, -i, and -e
remain blocked as interactive operations; use git add <paths> for whole files.
Flags on a later shell command do not make staging interactive; a backslash-escaped
newline continues the same command and retains its interactive restrictions. A normal
git apply or git apply --index still writes the working tree and must use the content gate.
Read the reason and Suggestion:. The suggestion is the intended path (force-push →
--force-with-lease; rm node_modules → npm cache clean --force && npm install;
pkill node → target the PID or pkill -f 'wrangler dev'). Take it — but the safe flag is
not always the whole answer. For a destructive or history-rewriting op on a shared target
(force-push to main, dropping data), the block is also a prompt to check intent: confirm
the action is warranted and that you have explicit user authorization before proceeding.
Don't rewrite to dodge the regex. The pattern encodes a real hazard; evading it is the wrong move (and trajectory detectors watch for evasion).
Content-check blocks are introduced-only. A pre_block finding ([check_id] in the
reason) blocks only when your content has more instances of that finding's line text than
the file already had (multiset over whitespace-normalized text — moving a pre-existing
finding doesn't count; a brand-new file counts everything). Fix the line(s) you added,
then retry.
Legitimate suppression — only when a flagged line is genuinely deliberate:
// interlinked-ignore: <check-id> — <why>
(reason separator —, --, or –; comma-separate multiple check ids)..interlinked/verify-suppressions.json
({ "<file-or-glob>": { "<check>": {"reason","by","at"} } }).interlinked verify, and are
ratcheted/audited (visible exceptions, not silent bypasses).// interlinked: defer <check> -- reason acknowledges a finding without
suppressing it (still logged) — for pre-existing findings you're choosing not to fix now.[interlinked:sequence] pre_block (trajectory detectors such as
secret_read_then_network_call) is deferred the same way, but the marker must sit in the
call itself — the Bash command text, or the content you are writing — and must name the
detector id exactly. The call then runs and the harness logs
[interlinked:sequence-deferred] with your reason. These detectors latch on session
state, so without the marker one confidential read can block later outbound network calls.When NOT to suppress: [proven] findings (tsc/biome/gitleaks/semgrep actually ran) and
any destructive/security guard-rule block. Suppression directives affect checks, not
rules — you cannot // interlinked-ignore a git push --force block. If a rule is
wrong for your repo, the fix is config (below), not a comment. Don't add
@ts-ignore/biome-ignore to silence a [proven] check — that trips suppressions-unjustified.
Loopback requests after a sensitive read. The sensitivity guard and
secret_read_then_network_call share destination-aware classification. Explicit HTTP(S)
curl requests to localhost, IPv4 loopback (127.0.0.0/8), or IPv6 loopback (::1)
are exempt, including ordinary request/data/header flags. Simple nc/ncat/netcat loopback
connections are also exempt. Wget requires --max-redirect=0 and a supported simple
invocation. Each network command and destination must qualify: a local request never
exempts another remote request in the same shell command. Sensitivity remains unchanged.
LAN/private addresses, unknown destinations/options, shell expansions, curl redirect flags,
explicit proxies, alternate resolution and config-file options retain guarding. Explicit
environment assignments selecting proxies or client configuration also retain guarding.
This is a static command classifier; it does not inspect ambient client configuration or
prove what a local service does with received data. Other guards continue to apply.
Commit-risk nudge. On a real Bash git commit, [interlinked:jit][heuristic] warns
when the currently staged diff ranks at p90 or above against recent commits in this repo.
It is advisory, never blocks, and does not replace subsequent commit gates. Inspect it with
interlinked metrics jit --staged --message '<planned subject>'; see
interlinked-quality-gates for calibration, interpretation and snapshot limits.
An agent must not create a Git worktree to route around shared-workspace policy. Use the current workspace. If isolation is genuinely required, ask a human operator to provision an approved worktree; listing and cleanup of existing worktrees remain allowed.
[proven] vs [heuristic]The daemon watches protected policy files and literal in-workspace reservation
paths, then reconciles content identities periodically and at delivery boundaries.
Writes by a shell, editor or agent can create durable pending verification entries.
Writer identity is unknown; a reservation is not proof that its holder performed
the write. Glob reservations, symbolic links, inaccessible or oversized files
remain explicitly unmeasured. A stopped or unavailable observer cannot certify coverage.
harness coverage status --progress --json reads cached observation time, generation
and recovery counts without reconciling files or returning receipt history. Use it for
progress, not to certify current bytes. Plain harness coverage status --json still
reconciles and returns full evidence. Neither status command starts recovery.
Receipts with nonempty unavailable retain completed checks as partial evidence while
keeping the version pending. Retrying is not made unnecessary merely by having a receipt;
safe cross-run reuse needs the relevant check input identities.
harness coverage accept-policy <digest> records an explicitly reviewed current
policy identity. It neither runs checks nor restores old policy bytes. A later
Claude ConfigChange can refuse application of a differing identity, after the disk
write has happened. This is not an immutable trust anchor or a universal policy
reload interlock. Do not describe watcher readiness as baseline-integrity enforcement.
Native translations are recorded in .interlinked/hook-translations.jsonl by the
compiled entry point. A post-tool refusal may become context, continuation or batch
cancellation; observational events cannot enforce a denial. Copilot/Gemini approval
requests conservatively deny where native approval is not certified. Experimental
Antigravity uses force_ask; Antigravity and Windsurf refuse a required input rewrite
that their hook cannot apply. Read translation status separately from native enforcement.
Harness-disable trajectory detection reads executable command positions, so a mutation-test
filename containing kill is not a process-kill event. Failed commands do not establish a
successful disable. A later event served by the daemon retires process/socket-outage suspicion;
it does not retire a recorded weakening of disabled_rules. Shadow verdicts remain advisory
and do not include a misleading BLOCKED: prefix.
The optional-chain check uses syntax grouping: passing value?.field to a function and then
accessing that function's result is different from (value?.field).name. Without optional
TypeScript syntax support it cannot establish this finding; silence is not a clean verdict.
SQL migration checks likewise cannot prove a column absent from an interpolated column list.
For it.each(table)(name, callback), assertion checks inspect the callback, not functions
inside the data table. An unparsed callback cannot prove assertion absence; table assertions
also cannot satisfy an assertion-free callback.
Every warning is tagged. [proven] = a real compiler/linter/scanner/parser/test-runner
produced it — authoritative, fix it. [heuristic] = regex/AST-shape match that could be a
false positive — evaluate it. No tag = unknown check id (never guessed).
Visibility varies by runner. On the supported Claude and Codex gate surfaces, a block reason
is surfaced through the provider's blocking channel. Experimental adapters target their native
response or exit-code shapes, but registration is not proof that every provider version enforces
them identically. Claude and Codex use model-visible additionalContext only on events whose
contracts support it. A Claude PermissionRequest deny uses
hookSpecificOutput.decision = { behavior: "deny", message }, not PreToolUse's
permissionDecision; allow/ask abstain on stdout, and non-blocking PermissionRequest diagnostics
stay on stderr. Cursor uses additional_context on generic postToolUse. OpenCode
appends post-tool feedback to tool output; Pi appends it to tool_result and can notify an
interactive UI. Copilot remains stderr-only and some lifecycle events are observation-only.
Allow-warnings are easy to overlook — read them.
Codex Stop and SubagentStop request continuation only for an explicit harness
block. Its reason is preserved before any additional context or warnings. An
allow or ask decision emits no stdout; diagnostics stay on exit-0 stderr for
operator inspection, not model-visible continuation. These events have no native
approval channel. Pending hook-coverage evidence alone does not force another turn
or become checked by allowing completion; inspect coverage status separately.
Silence = no model-visible finding, NOT "everything was checked" (2026-08-27). A served,
clean PostToolUse result writes ZERO BYTES — no [interlinked:Bash] all clean (354ms) row, not
even an empty {} envelope — because runners render one hook row per response and parallel tool
calls multiplied that into unusable noise. Outage visibility is phase- and runtime-specific:
| Result | What you see |
|---|---|
| Clean PostToolUse result served by the daemon | nothing (recorded locally only) |
| Findings | one compact [interlinked:<check>] block |
| Block | the reason plus the affected target — including a block that carries no warnings |
| Daemon unavailable, packaged PreToolUse runtime | an explicit evaluator skipped diagnostic; code edits also report function-token enforcement as not measured |
| Daemon unavailable, generated PostToolUse runtime | may remain model-silent while recording local no_harness status |
typescript unresolvable (--omit=optional install) | a [interlinked:self_import] NOT MEASURED warning on every JS/TS edit — that pre_block check ran no scan and never guesses; the cyclomatic gate degrades to the regex walker; the daemon startup warning names both |
self_import cannot place the file in ONE project | the same NOT MEASURED warning on that edit only, naming the cause: a tsconfig on the walk (nearest config, its extends, its references, its sibling tsconfig*.json files) could not be parsed; two projects both claim the file; the reference walk hit its 32-project bound with projects unvisited; no config in reach claims the file by a files/include pattern (a .js/.jsx/.mjs/.cjs importer is matched by pattern even when the project never enables allowJs — a JS self-import is a runtime fact); or the importer's directory is not on disk yet. The check resolves with the project's own compiler options and caches nothing, so fixing the config applies on the next edit. In a batch (write --batch, multi-edit, verify-changeset) it sees the whole proposed batch, and the baseline is judged against the disk |
a batch rewrites a file of the project's configuration graph — the selected tsconfig, any file its extends chain reaches (whatever it is called, base.json included), or any tsconfig*.json / jsconfig.json / package.json | each TypeScript entry of that batch carries a type checker cannot see the proposed configuration row (error for transactional callers) — the checker reads the disk's config; land the configuration change first, then the sources |
| a batch includes a member whose bytes equal the disk beside members that differ | the unchanged member is still type-checked, against the proposed siblings (a changed exporter can break an untouched consumer); multi-edit validates every manifest member and writes only the changed ones. Only a batch whose members are ALL unchanged skips the check. A config member whose bytes equal the disk is not a configuration rewrite |
| the type checker cannot place the file in ONE project | type checker unavailable (project_orphan: …) or (project_ambiguous: …) on that edit — a warning on the hook path, an error for transactional callers. The compiler judges each file under the SAME project self_import selects (nearest config, references, sibling tsconfig*.json, membership by pattern; one program per governing config) and never under the project root's config by guess. Its disk baseline is recomputed on every check, so a dependency repaired or broken on disk is reflected the next time the file is judged. A warm compiler service is rebuilt when the CONTENT of any file of its configuration graph (the tsconfig or an extends target) changes on disk, re-reads the project's root files on every reuse (a declaration file added or removed on disk joins or leaves the program), sees a dependency rewritten on disk by its compiler TEXT (every UTF-16 code unit is preserved, including lone surrogates; a touch that changes no text does not count; each check reads a file once, so what it compiles is what it fingerprinted), forgets a proposal the moment its check ends (a refused proposal never colours a later check), and a configured project with no source on disk yet still measures the first source written into it |
No output therefore never proves the full daemon check set ran. To distinguish a served clean
result from a silent degraded PostToolUse path, ask interlinked harness status or
interlinked doctor; inspect .interlinked/activity.jsonl and the statusline for the per-call
state.
The block row is load-bearing: the response path decides on the DECISION, never on whether the warning list happens to be non-empty. If you are about to rely on "the harness would have caught it", confirm the daemon is answering first — silence is not evidence that it was.
Test edits use the same hook loop. Deterministic introduced test theatre (assertion-free or tautological cases, SUT self-mocking, focus markers, unconditional skips) blocks before the write. Lower-confidence but low-noise test-quality findings are PreToolUse warnings so even a small writer can correct them immediately; context-heavier suite review stays in PostToolUse. Do not silence a warning merely to land the edit—assert a precise observable behavior, or justify the actual public compatibility contract.
With explicit structural_checks.test_first_mode: "enforce", an empty repository
does not exempt new JS/TS or Python source from companion-test requirements. Write
the first meaningful test, observe its failure, then implement the behavior. Existing
pair-debt workflow remains available after a suite exists; warning mode stays advisory.
This creation gate does not itself prove that every later change followed red–green TDD.
Test-discrimination advisories are post warnings and also run under
verify --all-checks. They review fallback-only evidence, indistinguishable
positive/negative expectations, unpinned spy arguments, wildcard observables,
in-tree fixtures, fixed ports, ambiguous throw messages, unguarded catch assertions,
vacuous loops, mock return echoes, duplicate bodies, spies without restoration,
export existence smoke tests, and commented-out assertions. They never block.
Evaluate the intended contract: forwarding a mock value or preserving an export
can be legitimate. Fix a detector false positive before rewriting valid tests.
spy_without_restore recognizes applicable local cleanup and statically discovered
Vitest/Jest restoration settings; clearAllMocks/resetAllMocks are insufficient.
Fresh test-owned array literals are exempt, as are fresh test-owned objects;
module/suite arrays, shared aliases, and prototype methods still need restoration.
Source headers and docs/design/test-discrimination-checks.md describe limits and
calibration. A corpus hit count is not precision or proof of a surviving mutant.
Do not trust a tool name as proof that no file changed. For Bash and other potentially mutating
tools, the daemon snapshots Git-visible files plus standalone ignored local files (for example
.env, while collapsing bulk ignored directory trees) before the call and attaches the observed
created/modified/deleted ChangeSet after it. PostToolUse file checks prefer those observed paths over
command text or runner-declared paths.
A read-only tool contributes no observed paths. The ChangeSet is a diff of the window the call
occupied, not a record of what the call did, so on Read / Glob / Grep / WebFetch /
WebSearch / TodoRead / NotebookRead / ListFiles every path in it was written by somebody
else — another agent on the same tree, a background test run, a watcher. Those calls are charged
nothing and run no file checks. Bash is NOT in that set and keeps its ChangeSet (it is the
bash-edit obligation channel), and neither is an unknown tool, so a new writer cannot open the
bypass by using an unfamiliar name. The list is one definition, at
src/lib/hook-read-only-tools.ts. Claude Code's PostToolUse hook is also registered only for
Write|Edit|MultiEdit|NotebookEdit|Bash; Codex keeps the all-tools matcher because apply_patch
arrives through it. Reservation handling parses every apply_patch section destination and move
source once; PreToolUse grants and PostToolUse idle-release scheduling consume the same ordered,
de-duplicated path list. It preflights that full list before granting, so a later blocking path
cannot strand an earlier lease when the tool never runs and therefore has no PostToolUse.
A Stop-time [interlinked:effect-residue] warning means a
PostToolUse was missing/unreconciled; the observed files were added to the touched-file rescan.
Effects reconciled by another actor are excluded (the warning reports the excluded count) — this
includes another session and a sibling subagent that shares the same Codex session id, so one
parallel actor is not charged for another's work. Actor identity is evidence-bounded: Interlinked
uses the stable subagent id when Codex supplies one and otherwise stays at root-session scope.
Persisted pre-upgrade rows with no actor field remain conservatively session-scoped; new rows
distinguish a known root from a known child across daemon restarts.
The noisy .interlinked/ runtime tree stays collapsed, but its exact local policy/control files are
observed and cannot be silenced by skip_paths.
Interpret [interlinked:sandbox] as evidence visible to the hook:
attested — the runner marked this call sandboxed;configured — restrictive client config was found, but a CLI/profile override may differ;disabled — the call/config explicitly selected unsandboxed/escalated execution; orunknown — no trustworthy evidence reached the hook.A workspace-write sandbox limits blast radius but still writes the real project, so it is
defense-in-depth, not rollback. Do not rerun or rewrite a command to evade this warning. For changes
that require rejection before disk, use Edit/Write or gated interlinked write/multi-edit.
The transactional commands share the content gate and commit lock. They capture target state
before checking, abort on target drift, preserve modes, and refuse rollback over newer edits.
Unavailable Biome or TypeScript checks abort a transaction. Re-read and re-gate a conflicted
proposal; consult interlinked-verify for the command contracts and transaction limits.
The observer is bounded ordinary-process evidence: concurrent writers can cause conservative extra
attribution, and an incomplete snapshot is never proof of absence.
The separate Bash compiler accelerator recognizes tsc / npx tsc only in
executable positions of flat, quote-aware command lists. Quoted search patterns
and look-alike names are never compiler invocations. Commands with comments,
newlines, substitutions, or grouping run unchanged through the normal tool.
The guard intercepts Grep tool calls and Bash rg/grep, queries a trigram index for
candidate files, and can answer the search directly (block-and-answer) faster than a full
scan. It is strictly never-worse-than-native: on small/medium repos it declines and native
rg runs unaccelerated. It's a large-monorepo optimization; you don't manage it. Inspect what
it would match with interlinked index query <pattern>. Your own just-written edits are
immediately searchable (in-memory dirty layer).
Config lives in .interlinked/guard-rules.json (team) + .interlinked/guard-rules.local.json
(personal, gitignored). Merge priority: local > team > built-in.
disabled_rules: ["builtin-git-force-push", …] — turn a built-in rule off (built-ins can be
disabled, never edited).extra_exceptions: { "<rule-id>": ["substring", …] } — the rule won't fire if the command
contains a listed substring.watchFile); interlinked harness restart
forces a full reload.Hook invocations record their transport outcome in
.interlinked/hook-transport.jsonl: daemon means a valid daemon response,
cold means fallback, and suppressed means intentional repeated-Stop
suppression. Correlate event_id, session_id, native_event, hook_pid,
socket_path and protocol. The hook PID identifies the receipt writer;
verify daemon PID ownership separately. Daemon activity rows carry
writer: "daemon"; the generated hook also writes activity, so an unmarked
activity row is not proof of daemon handling.
If the configured daemon socket is unreachable — including a zombie with a live process but
dead listener — every ordinary hook phase enters a cross-process, single-flight recovery path.
Daemon absence alone does not blanket-block safe reads, diagnostics, or repair work. The
current call proceeds in degraded mode after the inline deterministic subset runs:
merge-conflict markers, destructive commands, package installs, graph-shard protection,
and file-dump limits. Native fallback also enforces local guard-prediction reconciliation;
physical file size only advises. Checks needing the full evaluator are explicitly
unavailable rather than silently reported clean.
interlinked harness status, harness start / restart, doctor, disable, and the exact
interlinked install-hooks --refresh --preserve-mode repair remain executable during an outage
and do not race the automatic launch. A recovery message says “launch attempted” only when this
hook actually spawned; lock/backoff/stand-down paths do not claim that a supervisor is bringing
anything back. A valid guard-disabled.json or guard-disabled.local.json marker suppresses
self-heal. interlinked harness status confirms recovery — a red ZOMBIE line means a live PID
is answering nothing, so trust the socket probe over the PID. See interlinked-setup for the
liveness states and startup-failure ledger.
If the installed hook binary itself is missing or broken, the self-contained wrapper follows the
same no-deadlock boundary: Claude/Codex reserved read builtins and Cursor's dedicated
beforeReadFile event proceed with an explicit degraded warning. Ambiguous generic names on
other gates do not. Exact build/status/repair/non-destructive-disable commands remain available,
while mutating and unknown tools receive the provider's native deny response. Shell commands are
never inferred read-only from their text. Restore the runtime before relying on the full evaluator.
sleep is blocked" claim in old docs is stale — there is no standalone sleep
block in the current rule set. Don't rely on it.interlinked harness test "<cmd>" fires a synthetic event at the daemon to see if it
would block — and is itself exempt from destructive rules so you can test them safely. A
chained destructive tail still blocks; "test allowed it" ≠ "the real command is allowed".harness restart clears per-session trajectory state — soft_block "retry allowed" memory
and trajectory detectors reset. A restart mid-task can change guard behavior.check-executions.jsonl as
hook-observation.v1 rows (kind: trajectory), without model warnings. They preview
possible rules and do not enforce the reported action. Enforced trajectory rules remain active.ask becomes deny, while both providers' PermissionRequest ask preserves the
native user prompt. Codex Interrupt is asynchronous observation only: it emits zero stdout
and never runs Stop/SessionEnd cleanup. Cursor, Copilot, and Gemini
adapters can register and parse events, but their end-to-end provider enforcement remains
experimental (no provider-level contract test). Copilot/Gemini collapse ask → deny.
OpenCode and Pi are experimental managed bridges: OpenCode hard-gates generic tool execution
but ask denies and its permission/Stop signals cannot control the provider; Pi gates both
tool_call and direct user_bash, asks through an interactive UI, and denies headless.
Neither has dedicated native MCP, subagent, or worktree lifecycle events. Do not infer an
absent event from silence; the shared git worktree add shell block still applies.interlinked scanner on|off|toggle|status|review.INTERLINKED_DISABLE_PACKAGE_GUARD=1,
INTERLINKED_DISABLE_BASELINE_GUARD=1, INTERLINKED_DISABLE_SCRATCH_GUARD=1.interlinked harness test "git push --force" # would this block? (safe to run)
interlinked harness checks # authoritative check inventory
interlinked harness status --json # liveness + socket_answered
interlinked index query "<pattern>" # what the grep accelerator would match
interlinked harness restart # reload everything (clears trajectory)interlinked verify, and how to land edits through the gates.doctor, config.© QuentinCody, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
SKILL.md and 1 other file in skills/interlinked-harness of QuentinCody/interlinked-cli.
Open the folder on GitHubat commit 31b5259
Interlinked Harness next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Interlinked Harness this skillQuentinCody/interlinked-cli | 178 | — | ~11k | Automated safety check: Warn | MIT | |
| Hook Development for Claude Code Pluginsanthropics/claude-plugins-official | 38k | 10 repos | ~4.1k | Automated safety check: Notes | Apache-2.0 | |
| Plugin Settings Patternanthropics/claude-plugins-official | 38k | 7 repos | ~3k | Automated safety check: Pass | Apache-2.0 | |
| Mole Bug Patternstw93/Mole | 70k | — | ~2k | Automated safety check: Pass | GPL-3.0 | |
| Neat-Freak Knowledge CloseoutKKKKhazix/khazix-skills | 21k | — | ~1.9k | Automated safety check: Pass | MIT | |
| E2Ecallstack/react-native-pager-view | 3.4k | 1 repos | ~2.1k | Automated safety check: Pass | MIT |
anthropics/claude-plugins-official
Explains how to write Claude Code plugin hooks, both prompt-based checks and bash commands, for events such as PreToolUse, Stop and SessionStart.
anthropics/claude-plugins-official
Shows how Claude Code plugins keep per-project settings and state in .claude/plugin-name.local.md files with YAML frontmatter and a markdown body.
tw93/Mole
A catalog of recurring bug shapes in the Mole Mac cleaner, used to review safety-sensitive diffs for deletion safety, unbounded commands, shell traps and weak tests.
KKKKhazix/khazix-skills
Brings project docs, agent rule files, authorized memory and leftover workspace files back in line with what the code and runtime actually do at the end of a work session.
callstack/react-native-pager-view
Agentic end-to-end tests with e2e, the e2e runner. An agent skill from callstack/react-native-pager-view.
automazeio/ccpm
Runs a spec-driven workflow from PRD to epic to GitHub issues to parallel agents, with status, standup and blocked-work reports from bundled scripts.
QuentinCody/interlinked-cli
Package, test, and operate the experimental Interlinked Cowork plugin.
QuentinCody/interlinked-cli
Overview and router for the Interlinked CLI — a local guard, quality-enforcement, simplification-review, semantic-code-search, and observability layer for AI coding agents.
QuentinCody/interlinked-cli
Coordinate with other agents/humans via the optional Interlinked MCP Server, and use local checkpoints & file reservations.
QuentinCody/interlinked-cli
Investigate agent activity and JSONL/gzip evidence. An agent skill from QuentinCody/interlinked-cli.
QuentinCody/interlinked-cli
Install and operate Interlinked's optional local semantic function index.
QuentinCody/interlinked-cli
Keep prose specs and design docs honest against the code using Interlinked's spec-audit system.
Works with
Understand and respond to the Interlinked PreToolUse guard — the local daemon that BLOCKS dangerous tool calls before they run. Interlinked Harness is an agent skill from QuentinCody/interlinked-cli. Understand and respond to the Interlinked PreToolUse guard — the local daemon that BLOCKS dangerous tool calls before they run.
Run `npx skills add QuentinCody/interlinked-cli --skill interlinked-harness -a claude-code`. Or copy the skill folder (skills/interlinked-harness in QuentinCody/interlinked-cli) into .claude/skills/interlinked-harness in your project. Claude Code loads it when a task matches its description.
Run `npx skills add QuentinCody/interlinked-cli --skill interlinked-harness -a codex`. Or copy the skill folder (skills/interlinked-harness in QuentinCody/interlinked-cli) into .agents/skills/interlinked-harness in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add QuentinCody/interlinked-cli --skill interlinked-harness -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/interlinked-harness, .gemini/skills/interlinked-harness, .github/skills/interlinked-harness and .opencode/skills/interlinked-harness in your project.
Going by SKILL.md and its folder, Interlinked Harness needs the command-line tools its instructions call (git, curl, npm, docker, kubectl and terraform). Our summary lists: Python 3; Docker.
SKILL.md contains no URLs. Its commands use git, curl, npm, docker and npx, which can reach the network depending on how they are called. This is read from the text; nothing was executed.
Our automated static check of SKILL.md flagged 1 warning(s): mentions a credentials file (ssh keys, cloud or package-manager tokens). Read the flagged lines before installing; the check is not a guarantee either way.
Interlinked Harness is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 11k tokens (SKILL.md is roughly 42k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Interlinked Harness: Hook Development for Claude Code Plugins (anthropics/claude-plugins-official, 38k stars), Plugin Settings Pattern (anthropics/claude-plugins-official, 38k stars), Mole Bug Patterns (tw93/Mole, 70k stars) and Neat-Freak Knowledge Closeout (KKKKhazix/khazix-skills, 21k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
QuentinCody (a GitHub user) maintains it in QuentinCody/interlinked-cli, which has 178 GitHub stars. The repository holds 12 skills in this directory. The repository was last updated on October 2, 2026.
Source: QuentinCody/interlinked-cli on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.