From 41de51656a9bf8cab20e8c1e4a157751999ed927 Mon Sep 17 00:00:00 2001 From: choiyounggi <74581798+choiyounggi@users.noreply.github.com> Date: Wed, 12 Aug 2026 22:30:24 +0900 Subject: [PATCH] knowledge: ingest 2 verified insight(s) --- .dev-loop/INGEST_REPORT.md | 208 ++++++++++++++---- log.md | 1 + .../dispatching-after-a-completion-report.md | 84 +++++++ .../session-completion-gates.md | 2 +- wiki/infrastructure/index.md | 1 + wiki/platforms/index.md | 1 + ...command-text-inspected-before-execution.md | 2 +- .../agent-permission-classifier-denials.md | 85 +++++++ .../tools/harness-mediated-tool-results.md | 2 +- 9 files changed, 340 insertions(+), 46 deletions(-) create mode 100644 wiki/infrastructure/agent-orchestration/dispatching-after-a-completion-report.md create mode 100644 wiki/platforms/tools/agent-permission-classifier-denials.md diff --git a/.dev-loop/INGEST_REPORT.md b/.dev-loop/INGEST_REPORT.md index 55ccfd1..656a967 100644 --- a/.dev-loop/INGEST_REPORT.md +++ b/.dev-loop/INGEST_REPORT.md @@ -1,53 +1,175 @@ -# Knowledge consolidation — 15 open PRs (#17–#40) → one reconciled state +# Knowledge flush — 3 insight(s) -The 15 open `knowledge/*` PRs (created 2026-08-04 → 2026-08-05, before the -harvest processed-store dedupe fix in #41) contained 123 file-versions of ~75 -unique pages, with the same insight landing at up to 3 different paths across -up to 8 PRs. Per-PR review would re-import those duplicates, so — as with the -#6–#13 consolidation — this branch carries the reconciled end-state and the 15 -PRs are closed in its favor. +Queue drained: `~/.dev-loop/queue/3b771258-….jsonl`, 3 pending candidates, all +orchestration/harness-platform. **2 ingested here, 1 folded into PR #64.** ## Verified best-practice -Every adopted page's sources were carried from its originating PR's flush, where -they were live-verified at flush time; no new URLs were introduced during -consolidation (checked mechanically: every `http(s)` URL in every merged page -appears in a source PR's diff; every added body line in amended pages traces to -a source PR hunk — orphan-line verification). Confidence fields were kept as the -originating flushes set them, except client-side-rate-limiting where the union -of provider-doc citations (Okta, Auth0, GitHub, OpenAI, RFC 6585) supports -`verified` for the load-bearing claims. One subagent's fabricated content (12 -files matching neither main nor any PR, with invented source URLs) was detected -by the same verification and replaced with true PR content. +### C1 — a worker paused by a provider usage limit *(folded into PR #64, not in this PR)* + +**Claim.** When several workers billed to one account go quiet at once, look for the +`You've hit your … limit · resets …` marker before classifying the stall; wait for the +stated reset, then resume with a prompt that names the state re-check, the remaining +done-criteria, and the completion signal. + +**Sources checked.** +- `https://code.claude.com/docs/en/errors` — the three marker forms appear **verbatim** + (`You've hit your session limit · resets 3:45pm`, `… weekly limit · resets Mon 12:00am`, + `… Opus limit · resets 3:45pm`); "Claude Code **blocks further requests** until the reset + time"; "The session and weekly limits are **shared across all models**, so switching models + doesn't restore access"; "The Opus limit **applies only to Opus requests**". +- `https://code.claude.com/docs/en/costs` — "a per-seat allowance that resets on a rolling + five-hour window and a weekly window… shared with Claude chat and Cowork", and "a single + burst of heavy activity, such as a large workflow fanout, can exhaust the weekly allowance". + This is the mechanism behind a *synchronized* multi-worker stop. +- `https://github.com/anthropics/claude-code/issues/5977` — the "continue"-loses-context + failure the re-orient prompt exists to prevent (closed as duplicate). +- `https://github.com/anthropics/claude-code/issues/36320` — auto-resume is still an open + feature request, so the orchestrator owns the re-drive. + +**Confidence: verified.** The candidate's marker string matched official docs exactly, and +the docs added a correction the field observation did not have: an *Opus* limit **is** cleared +by `/model`, while session/weekly limits are not. + +### C2 — reusing a worker's terminal after its completion report + +**Claim.** `worker_done` settles the task, not the terminal; gate the next `worker-start` +on `orca terminal wait --for tui-idle`, and recover a failed start through a new/linked +dispatch rather than re-running it, because 3 consecutive failures circuit-break the task. + +**Sources checked — read live from the installed CLI this session, not from memory:** +- `orca skills get --topic orchestration --full` — "Wait for `tui-idle` before dispatching" + (line 377); "After processing each accepted `worker_done`, choose the terminal's next owner + before you acknowledge the Delivery or wait again… `worker-start --task + --terminal ` so Orca transfers cleanup ownership to the new Dispatch. Otherwise run + `orca orchestration worker-release`" (line 242); "After 3 consecutive failures on one task, + the dispatch context circuit-breaks and the task is marked failed" (line 176); "Treat a + `check --wait` timeout or `{count:0}` as a checkpoint, not a worker failure" (line 146). +- `orca orchestration worker-start --help` — "The call exits 0 only for ready. Failed or + outcome_unknown exits 1 and JSON includes stage/failedStage, setup, effects, + residualResources, and recovery commands"; "--retry-of links the replacement attempt but + does not inherit placement". +- `orca terminal wait --help` — `--for exit|tui-idle` confirmed. + +**Confidence: verified** (official tool guide + reproducible `--help` output, plus a +2026-08-06 field reproduction: two dispatches issued immediately after `worker_done` both +failed `runtime_unavailable` and consumed an attempt; both succeeded after an idle check). + +### C3 — a classifier denies a tool call the agent was told to make + +**Claim.** Read the denial as one of four tiers; only `soft_deny` is cleared by an `allow` +entry or by the user naming the exact action in their next message. Write permission/`autoMode` +config at **user** scope. + +**Sources checked.** +- `https://code.claude.com/docs/en/auto-mode-config` — confirmed **verbatim**: the four-tier + precedence; "Explicit user intent overrides the remaining soft blocks: if the user's message + directly and specifically describes the exact action Claude is about to take, the classifier + allows it even when a `soft_deny` rule matches"; "General requests don't count as explicit + intent"; "The classifier doesn't read `autoMode` from project settings in + `.claude/settings.json` or `.claude/settings.local.json`… Before v2.1.207, the classifier + also read `.claude/settings.local.json`"; the `"$defaults"` splice and what omitting it + discards; `classifyAllShell` (v2.1.193+); the `Blocked by classifier` fixed reason (v2.1.208+) + and `defaults --label`; `/permissions` → **Recently denied** → `r`. +- `https://github.com/anthropics/claude-code/issues/58222` — *"Auto-mode classifier blocks + authorized operator workflows"*, closed as **not planned** (state re-checked via the API this + session). +- `https://github.com/anthropics/claude-code/issues/64128` — *"`allow` rules silently ignored… + self-modification block contradicts documented escape hatch"*, closed as **not planned**. + +**Confidence: verified.** Note the page deliberately does **not** adopt the candidate's stronger +directive ("fresh explicit consent makes the same edit pass"). The 2026-08-09 field observation +varied **two** variables at once — settings scope *and* consent — so it cannot isolate consent as +the cause, and the docs say the classifier ignores project-scoped `autoMode` entirely. The page +records the observation as supporting the documented precedence, and keeps the reported +self-modification denials (which persisted *despite* prior-turn authorization) as an edge case. ## Existing-layer check -- Merged-main near-dup scan before consolidation: pairwise Jaccard over - title + "When this applies" across all 141 merged pages → **0 flagged pairs**; - previously merged content carries no duplication. -- Cross-PR dedup during consolidation: 10 duplicate clusters collapsed to one - canonical page each (rate limiting 8→1, call-site enumeration 7→folded into - the canonical merged in #20, stderr/exit-0 diagnostics 4→1, sysroot 2→1, - env-off-switch 2→1, completion predicates 2→1, robots.txt 2→1, - harness-mediated results 2→1, leaked artifacts 2→1, orchestration category - naming unified). Three near-pairs kept distinct after trigger comparison, - with mutual `related:` links (differential setup vs interpretation; expansion - semantics vs off-switch design; import-time tactics vs level choice). -- 24 existing pages received union-merged amendments; additions already present - in main (from #16/#20) were skipped, and all non-canonical `related:` ids - were remapped to canonical page ids (post-merge broken-link scan: 0). +Routed both ingested candidates via `INDEX.md` → **infrastructure** (multi-agent orchestration) +and **platforms** (agent-harness tooling), then read every page in those categories whose +"load when" line overlaps. + +Pages read: infrastructure-agent-orchestration-control-signals-vs-primary-artifacts, infrastructure-agent-orchestration-shared-run-state, infrastructure-agent-orchestration-pane-delivery-confirmation, infrastructure-agent-orchestration-session-completion-gates, infrastructure-agent-orchestration-worktree-isolated-workers, infrastructure-agent-orchestration-dispatching-after-a-completion-report, platforms-tools-agent-permission-classifier-denials, platforms-tools-harness-mediated-tool-results, platforms-tools-bsd-vs-gnu-cli, platforms-tools-version-keyed-artifact-cache, platforms-shells-command-text-inspected-before-execution, platforms-processes-driving-a-tui-in-a-tmux-pane + +**Overlaps and what happened.** + +| Candidate | Nearest existing page | Verdict | +|---|---|---| +| C2 | `control-signals-vs-primary-artifacts` covers *is the worker alive/done/dead*; `session-completion-gates` covers *blocking a session from ending* | Neither covers reusing a **settled dispatch's terminal**. New page. | +| C3 | `harness-mediated-tool-results` covers a harness returning **substitute content**; `command-text-inspected-before-execution` covers a **rule-based text gate** | Neither covers a **model-based** second gate with tiered escapes. New page, cross-linked to both. | +| C1 | see Open-PR check | Folded into PR #64. | + +**Correction worth flagging to the reviewer.** Both ingested pages already existed as +**untracked files in the shared flush checkout** (`~/.dev-loop/repo`) — drafted by an earlier +flush session that never committed or PR'd them. `git log origin/main -- ` returns +nothing for both, and no open `knowledge/*` head contains either. That is almost certainly why +these two candidates were still `pending` in the queue. This PR commits them for the first +time; I re-verified every cited source from scratch (see above) rather than trusting the drafts, +and refreshed `last_verified` to 2026-08-12. + +**Conflicts flagged:** none. Neither page contradicts an existing directive. + +**Related-links added (both ways, per wiki-ingest step 7):** `session-completion-gates` → +`dispatching-after-a-completion-report`; `harness-mediated-tool-results` and +`command-text-inspected-before-execution` → `agent-permission-classifier-denials`. Reciprocal +links into `control-signals-vs-primary-artifacts`, `pane-delivery-confirmation`, +`shared-run-state`, and `worktree-isolated-workers` were **deliberately skipped**: PRs #47, #51, +and #64 are all editing exactly those `related:` lines, and adding a fourth edit guarantees a +merge conflict. The forward links from the new pages already resolve, so no invariant is broken. + +**Pre-existing lint finding, left alone (not introduced here):** +`wiki/mobile/release/staged-rollout-and-hotfix.md` has `related: [mobile-performance-startup-time]`, +which resolves to no page on `main`. + +**Invariants re-checked after the edit:** every `related:` id and inline `[page-id]` in the two +new pages resolves; both new pages are listed in their domain `index.md`; both bodies are 72 +lines (≤120); `log.md` appended. + +## Open-PR check + +Listed all 20 open `knowledge/*` PRs and diffed each head's `wiki/` against `main` +(7 of them are fork PRs whose heads are not on `origin` — those were fetched via +`pull//head`, which is why a naive `git fetch origin ` shows them as empty). + +Heads touching either target area: **#64, #47, #51** (`agent-orchestration`); **none** +touch `platforms/tools/`. + +| Candidate | Overlapping head | Verdict | +|---|---|---| +| C1 usage-limit stall | **#64** — its new `unattended-worker-questions.md` already carries the case as one row of its stall-classification table (*"A usage-limit or re-auth notice → Idle waiting, not a crash; resume after the stated reset"*) | **fold** | +| C2 dispatch-after-completion | #47 / #51 touch `control-signals` and `worktree-isolated-workers`; neither adds terminal-reuse content | **new** | +| C3 classifier denial | none | **new** | + +**Fold executed, not just noted.** Pushed to `knowledge/choiyounggi-20260808-004155` (PR #64): +a new `usage-limit-paused-workers.md` (73 body lines) plus that branch's index row, the +reciprocal `related:` link, and its `log.md` entry; #64's one-line row now points at the page +instead of being duplicated here. Commented on #64 explaining the fold. **No sibling duplicate +PR was opened for C1.** + +While doing this I noticed my first fold commit had swept the two untracked leftover drafts +into #64 via `git add wiki/`. That commit was amended and force-pushed, so **#64's diff is back +to its own scope plus the one folded page** — worth a glance when you review it. ## Routing decision -- New categories: `infrastructure/agent-orchestration` (5 pages; unified the - competing `orchestration`/`agent-orchestration` names), `databases/data-survey` - (1), `qa/deliverables` (1). All other pages route into existing categories. -- Canonical-path decisions: rate limiting → `backend/common/reliability/` - (sits beside timeouts-and-retries; 6 of 8 variants chose it); stderr - diagnostics → `platforms/processes/` (concern spans beyond shells); leaked - artifacts → `testing/data/artifact-leakage-from-a-suite`; call-site - enumeration → the existing `backend/common/change-impact/` page. -- All 38 new pages listed in their domain indexes (nearest-index rule; backend - routes via its python sub-index for bytecode-cache-staleness); INDEX.md domain - summaries updated for infrastructure/qa/databases. Full-wiki lint: frontmatter, - ids, related-links, index coverage, size, qualifiers, staleness → 0 findings. +| Insight | Target | New category? | +|---|---|---| +| C1 usage-limit stall | `infrastructure/agent-orchestration/usage-limit-paused-workers` — **on PR #64's branch** | No | +| C2 dispatch-after-completion | `infrastructure/agent-orchestration/dispatching-after-a-completion-report` — **this PR** | No | +| C3 classifier denial | `platforms/tools/agent-permission-classifier-denials` — **this PR** | No | + +**Domain re-routing.** All three candidates carried the harvested hint `domain: platforms`. +C1 and C2 were re-routed to **infrastructure**, whose `INDEX.md` line explicitly owns +"multi-agent orchestration (worker liveness signals, shared run state, tmux pane delivery, +completion gates, worktree-isolated workers)"; `platforms` owns OS/shell/tool-invocation +differences, which is where C3 belongs (`tools`, alongside `harness-mediated-tool-results` — +both are about an agent harness altering what a tool call does). + +No new category was needed: `agent-orchestration` and `platforms/tools` both already exist and +cover these triggers. + +## Queue + +All 3 rows retired to `~/.dev-loop/queue/.processed.jsonl` (2 ingested, 1 folded); the session +file is now empty and removed. diff --git a/log.md b/log.md index c930fc2..fa80cbe 100644 --- a/log.md +++ b/log.md @@ -43,3 +43,4 @@ Append-only. Format: `## [YYYY-MM-DD] --json`, then `worker-start --task --terminal ` — this transfers cleanup ownership to the new dispatch | +| No follow-up for that agent | `worker-release --dispatch ` | +| The user asked to keep the terminal live for debugging | `worker-retain --dispatch `, and release it later | + +4. **Read the failed start's receipt instead of retrying it.** `worker-start` + exits 0 only for `ready`; a failed or `outcome_unknown` start exits nonzero and + returns `stage`/`failedStage`, `effects`, `residualResources`, and recovery + commands. Fix the stage the receipt names, then start again. +5. **Retry the same task through a linked replacement dispatch**, naming + placement explicitly, because retry does not inherit it: + `worker-start --task --retry-of --terminal ` + (or `--on`/`--worktree` plus `--agent`). +6. **Count the failures against the task.** After 3 consecutive failures on one + task the dispatch context circuit-breaks and the task is marked `failed`. Blind + retries spend that budget on the same unmet precondition; an idle check spends + none of it. + +## Edge cases + +| Case | Then | +|------|------| +| The completion report arrives but the terminal never reaches idle | This is the settled-dispatch/live-terminal state, not a stall — hold the handle and let the release path own it; do not close the terminal to force it | +| The start receipt says `outcome_unknown` | `worker-stop --dispatch ` and inspect again, or `worker-abandon --dispatch ` while accepting that resources may still be live — abandon performs no remote, process, or filesystem action | +| The task already reached `failed` from the circuit breaker | Recovering it means an explicit `task-update`, or a new task carrying the same spec; a `--retry-of` dispatch does not un-fail a circuit-broken task | +| The target is a bare shell rather than an agent CLI | Omit `--inject`, dispatch for tracking, and send the prompt with `terminal send --text … --enter`; the idle gate still applies | +| `worker-release` returns `release_pending` or `release_unknown` | Follow the recovery action in the receipt; substituting `terminal close` closes a terminal whose ownership the orchestrator has not proven | +| The idle wait times out on a long-running agent | A timeout is a checkpoint, not a failure — coding tasks run 15–60 minutes; keep waiting rather than starting a competing dispatch | + +## Instead of + +| If you are about to | Do this instead | Why | +|---------------------|-----------------|-----| +| Start the next task the moment the completion report lands | Run the terminal idle check first, then start | The report settles the task; the previous dispatch still owns the terminal, so the start fails on an occupied runtime | +| Re-run the identical `worker-start` after it failed | Read the receipt's `stage`/`effects`/`residualResources`, fix that, then start with `--retry-of ` | Three consecutive failures on one task circuit-break it into `failed`, so a retry loop destroys the task it was meant to rescue | +| Let a `--retry-of` replacement pick its own placement | Repeat the intended `--on`/`--worktree` and `--agent`/`--terminal` choice on the retry | Retry links the attempt for provenance and deliberately does not inherit placement | +| Close the terminal yourself to free it for the next task | Transfer it with `worker-start --terminal ` or hand it to `worker-release` | The release path preserves inspectable output first and closes only the exact terminal the settled dispatch owns | + +## Sources + +- Orca CLI bundled skill guide, `orca skills get --topic orchestration --full` (app 1.4.177, command schema v1) — "Wait for `tui-idle` before dispatching"; "After processing each accepted `worker_done`, choose the terminal's next owner before you acknowledge the Delivery or wait again… run `orca orchestration worker-start --task --terminal --json` so Orca transfers cleanup ownership to the new Dispatch. Otherwise run `orca orchestration worker-release --dispatch --json`"; "After 3 consecutive failures on one task, the dispatch context circuit-breaks and the task is marked failed"; "It proves `failed` or `stopped`: start a replacement with `worker-start --task --retry-of ` plus an explicit `--on`/`--worktree` and `--agent`/`--terminal` choice. Retry does not silently inherit placement"; "Treat a `check --wait` timeout or `{count:0}` as a checkpoint, not a worker failure" +- `orca orchestration worker-start --help` (app 1.4.177) — "The call exits 0 only for ready. Failed or outcome_unknown exits 1 and JSON includes stage/failedStage, setup, effects, residualResources, and recovery commands when needed"; `orca terminal wait --help` — `--for exit|tui-idle`; `orca orchestration task-update --help` — statuses `pending, ready, dispatched, completed, failed, blocked` +- Field observation 2026-08-06 (dev-loop orchestration run, two occurrences): follow-up dispatches issued to a worker's terminal immediately after its completion report both failed on an unavailable runtime and consumed a dispatch attempt; the same start succeeded on the first try once the terminal was confirmed idle first diff --git a/wiki/infrastructure/agent-orchestration/session-completion-gates.md b/wiki/infrastructure/agent-orchestration/session-completion-gates.md index 08e93fc..985be3a 100644 --- a/wiki/infrastructure/agent-orchestration/session-completion-gates.md +++ b/wiki/infrastructure/agent-orchestration/session-completion-gates.md @@ -7,7 +7,7 @@ confidence: verified sources: - https://code.claude.com/docs/en/hooks last_verified: 2026-08-05 -related: [infrastructure-agent-orchestration-pane-delivery-confirmation, infrastructure-agent-orchestration-worktree-isolated-workers, platforms-processes-tool-diagnostics-without-a-failing-exit-code] +related: [infrastructure-agent-orchestration-pane-delivery-confirmation, infrastructure-agent-orchestration-worktree-isolated-workers, platforms-processes-tool-diagnostics-without-a-failing-exit-code, infrastructure-agent-orchestration-dispatching-after-a-completion-report] --- # A Gate That Blocks a Worker Session from Ending Mid-Workflow diff --git a/wiki/infrastructure/index.md b/wiki/infrastructure/index.md index 36a0771..127fd35 100644 --- a/wiki/infrastructure/index.md +++ b/wiki/infrastructure/index.md @@ -16,6 +16,7 @@ Match your situation to a "load when" line; load only matching pages. | [shared-run-state](agent-orchestration/shared-run-state.md) | Several agent/worker sessions coordinate through files in one repository (status directory, briefs, escalations, claim files); choosing the path layout for that state; starting an orchestration in a repo that may already have one running; a watcher woke on a task id it did not create; the default branch moved during a run | | [pane-delivery-confirmation](agent-orchestration/pane-delivery-confirmation.md) | An orchestrator drives another program through a terminal multiplexer (`tmux send-keys` + `capture-pane`) and must decide whether the input was consumed, retry, or escalate; a pane diff is being used as delivery evidence; the target echoes but never runs the input | | [session-completion-gates](agent-orchestration/session-completion-gates.md) | Writing a Stop/completion hook that blocks a worker session from ending while its phase is non-terminal; the gate fires on a worker that followed its own prompt; deciding the terminal phase set, the unknown-phase default, and how the gate bounds its own repetition | +| [dispatching-after-a-completion-report](agent-orchestration/dispatching-after-a-completion-report.md) | A worker reported completion and the orchestrator wants to hand that same terminal or runtime slot its next task; a start/dispatch call fails with a runtime-unavailable-class error moments after a completion report; a task reached a terminal `failed` status with no worker having worked on it; deciding a settled dispatch's next owner (transfer, release, or retain) and how to retry a failed start without spending the task's attempt budget | | [worktree-isolated-workers](agent-orchestration/worktree-isolated-workers.md) | Authoring the brief/output contract for parallel workers each confined to its own git worktree; workers stall at the same phase with no task-level error; deciding where shared or produced artifacts live and which direction (read vs write) a worktree guardrail stops | ## ci-cd diff --git a/wiki/platforms/index.md b/wiki/platforms/index.md index 8081da8..c56c591 100644 --- a/wiki/platforms/index.md +++ b/wiki/platforms/index.md @@ -26,6 +26,7 @@ Match your situation to a "load when" line; load only matching pages. | Page | Load when | |------|-----------| +| [agent-permission-classifier-denials](tools/agent-permission-classifier-denials.md) | A model-based permission gate (Claude Code auto mode) denied an action that is correct for the task — a push, a config write, a deploy command; writing the permission or `autoMode` config itself is refused as self-modification; choosing the settings scope a classifier actually reads; designing an automation whose steps a classifier will judge, and where the consent sentence has to sit | | [bsd-vs-gnu-cli](tools/bsd-vs-gnu-cli.md) | A command works on Linux but fails on macOS or vice versa (`date`, `sed -i`, `timeout`, `seq`, `grep -P`, `readlink`, `stat`); writing a script or CI step that must run on both userlands; deciding whether to install GNU coreutils on macOS or write POSIX-only | | [harness-mediated-tool-results](tools/harness-mediated-tool-results.md) | A plugin or hook in your agent harness returned substitute content for a built-in tool (truncated read, redaction, a note telling you to call something else); deciding whether a short result is interception or a genuinely small/empty file; the hook's suggested workaround failed too; briefing spawned worker sessions about a known-degraded tool | | [version-keyed-artifact-cache](tools/version-keyed-artifact-cache.md) | Shipping a code update to a distribution system that caches artifacts by a version string (a Claude Code marketplace plugin, or any tag-pinned cache) and the update runs but the old behavior persists; deciding why `/plugin update` reports "at latest" yet new code never runs; locating and clearing a stale `~/.claude/plugins/cache////` | diff --git a/wiki/platforms/shells/command-text-inspected-before-execution.md b/wiki/platforms/shells/command-text-inspected-before-execution.md index 9e62a9c..45be5be 100644 --- a/wiki/platforms/shells/command-text-inspected-before-execution.md +++ b/wiki/platforms/shells/command-text-inspected-before-execution.md @@ -8,7 +8,7 @@ sources: - https://code.claude.com/docs/en/hooks - https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html last_verified: 2026-08-06 -related: [platforms-shells-portable-shell-scripts, platforms-environment-path-resolution, platforms-shells-escapes-in-shell-string-literals, infrastructure-agent-orchestration-control-signals-vs-primary-artifacts, platforms-tools-harness-mediated-tool-results, platforms-processes-tool-diagnostics-without-a-failing-exit-code] +related: [platforms-shells-portable-shell-scripts, platforms-environment-path-resolution, platforms-shells-escapes-in-shell-string-literals, infrastructure-agent-orchestration-control-signals-vs-primary-artifacts, platforms-tools-harness-mediated-tool-results, platforms-processes-tool-diagnostics-without-a-failing-exit-code, platforms-tools-agent-permission-classifier-denials] --- # Commands Read as Text by a Gate Before the Shell Runs Them diff --git a/wiki/platforms/tools/agent-permission-classifier-denials.md b/wiki/platforms/tools/agent-permission-classifier-denials.md new file mode 100644 index 0000000..9d75bb3 --- /dev/null +++ b/wiki/platforms/tools/agent-permission-classifier-denials.md @@ -0,0 +1,85 @@ +--- +id: platforms-tools-agent-permission-classifier-denials +domain: platforms +category: tools +applies_to: [claude-code, agent-harness] +confidence: verified +sources: + - https://code.claude.com/docs/en/auto-mode-config + - https://github.com/anthropics/claude-code/issues/58222 + - https://github.com/anthropics/claude-code/issues/64128 +last_verified: 2026-08-12 +related: [platforms-tools-harness-mediated-tool-results, platforms-shells-command-text-inspected-before-execution, infrastructure-agent-orchestration-control-signals-vs-primary-artifacts, infrastructure-agent-orchestration-worktree-isolated-workers] +--- + +# A Classifier Denies a Tool Call the Agent Was Told to Make + +## When this applies + +An agent harness runs a second, model-based permission gate after its rule-based +one (Claude Code auto mode) and it denied an action that is correct for the task — +a push, a config write, a deploy command. Also when writing the permission or +`autoMode` config itself is denied as self-modification, or when you are designing +an automation whose steps a classifier will judge. + +## Do this + +1. **Read the denial as a tier, not as a verdict on the command.** Four tiers + decide it, and each has a different unblocking move: + +| Tier | Cleared by | Your move | +|------|-----------|-----------| +| `permissions.deny` (rule-based, runs first) | Nothing — not user intent, not the classifier | Change the rule, or do not do the action | +| `hard_deny` (classifier) | Nothing — `allow` and user intent do not apply | Change the action; the exfiltration boundary is the built-in entry | +| `soft_deny` (classifier) | An `allow` entry, or explicit user intent | Steps 2–3 | +| Everything else | Already allowed | — | + +2. **When the block is a soft one, have the user name the exact action in their + next message, then retry once.** The rule is specificity, not recency: "if the + user's message directly and specifically describes the exact action Claude is + about to take, the classifier allows it even when a `soft_deny` rule matches", + and "general requests don't count as explicit intent" — "clean up the repo" + does not authorize a force push; "force-push this branch" does. +3. **Design automations so the consent sentence is produced before the gated + step**: detect the need, ask one question naming the exact action, act only on + an explicit yes. A step whose authorization lives three turns back is judged + without it. +4. **Make a repeated block durable instead of re-consenting each time.** Add the + destination to `autoMode.environment`, or the pattern to `autoMode.allow`, and + confirm with `claude auto-mode config`. +5. **Write permission and `autoMode` config at user scope, from outside the + agent's own edit path.** The classifier reads `autoMode` from + `~/.claude/settings.json`, managed settings, and `--settings`; it does **not** + read `.claude/settings.json` or `.claude/settings.local.json`, because a + checked-in repo or a build step could otherwise inject its own allow rules. + A project-scoped write is therefore both denial-prone and inert. +6. **Recover a one-off denial through the harness's own retry path**: `/permissions` + → **Recently denied** → `r` marks it for retry, and Claude Code resumes the + conversation telling the model it may retry that call. + +## Edge cases + +| Case | Then | +|------|------| +| The user authorized it plainly last turn and it is still denied | Treat permission-config self-modification as a case explicit intent does not reliably clear: reported denials name "a permission widening the user did not explicitly request" for `.claude/settings.local.json` and "Writing to `.claude/settings.json` modifies the agent's own permissions configuration (Self-Modification)" even when a skill or the user prescribed the write. Have the user make the edit, or run the harness's own config command | +| An `allow` rule was added and the action is still blocked | An `allow` entry only overrides `soft_deny`; a `hard_deny` or a `permissions.deny` match is unaffected, and the classifier ignores `allow` written into project settings | +| The denial reason is the fixed string `Blocked by classifier` | v2.1.208+ scores severity instead of writing an explanation; infer the tier from the action and use `claude auto-mode defaults --label ''` to read the matching rule's wording | +| You set `autoMode.allow`/`soft_deny`/`hard_deny` without `"$defaults"` | The whole built-in list for that section is discarded, including the force-push, `curl \| bash`, production-deploy, and auto-mode-bypass soft blocks — include the literal `"$defaults"` | +| The boundary was stated only in conversation ("don't push until I review") | Context compaction can remove the message that stated it; put a `permissions.ask` or `deny` rule in settings for a boundary that must survive the session | +| A narrow `Bash(...)` allow rule lets an unreviewed argument through | Set `autoMode.classifyAllShell: true` so every shell command reaches the classifier; auto mode otherwise suspends only broad rules like `Bash(*)` | + +## Instead of + +| If you are about to | Do this instead | Why | +|---------------------|-----------------|-----| +| Rephrase and re-send a denied command until one wording passes | Get the exact action named by the user, retry once, then add an `environment` or `allow` entry | The gate judges the action, so re-rolling wordings burns turns without changing the tier | +| Widen your own permissions by editing `.claude/settings.local.json` | Have the user write `~/.claude/settings.json`, or use `claude auto-mode` subcommands | Self-modification of the permission config is what the block targets, and the classifier does not read project-scoped `autoMode` anyway | +| Ask for blanket approval up front ("you can run these commands") | Ask one question naming the specific action right before the step | Reported blocks cite blanket phrasing as not explicit authorization "for this specific push" | +| Treat a denial as proof the task is disallowed | Identify the tier, then pick the matching mechanism from the table | Only `permissions.deny` and `hard_deny` are unconditional; the rest have a stated escape hatch | + +## Sources + +- https://code.claude.com/docs/en/auto-mode-config — the four-tier precedence (`hard_deny` unconditional; `soft_deny` overridable; `allow` as exceptions to `soft_deny`; "Explicit user intent overrides the remaining soft blocks: if the user's message directly and specifically describes the exact action Claude is about to take"); "General requests don't count as explicit intent"; "The classifier doesn't read `autoMode` from project settings in `.claude/settings.json` or `.claude/settings.local.json`… Before v2.1.207, the classifier also read `.claude/settings.local.json`"; `"$defaults"` splicing and the danger of omitting it; `classifyAllShell` (v2.1.193+); the `Blocked by classifier` fixed reason (v2.1.208+) and `defaults --label`; the `/permissions` → Recently denied → `r` retry path; conversational boundaries lost to compaction +- https://github.com/anthropics/claude-code/issues/58222 — eight operations denied despite authorization in the immediately preceding turn, including "Writing to .claude/settings.json modifies the agent's own permissions configuration (Self-Modification)" for a write a skill itself recommended, and "Git Push to Default Branch without explicit user authorization for this specific push" after "you can run these commands". Closed as not planned +- https://github.com/anthropics/claude-code/issues/64128 — "Self-Modification: adding new Bash permission allow rules to .claude/settings.local.json … a permission widening the user did not explicitly request", and the subsequently added allow rules not clearing the block (CLI v2.1.121). Closed as not planned +- Field observation 2026-08-09 (Claude Code 2.1.220, auto mode): the same `permissions.allow` + `autoMode` edit was denied against a project `.claude/settings.local.json` with no preceding instruction, and succeeded against `~/.claude/settings.json` immediately after the user named the edit. Two variables differed (settings scope and consent), so this observation supports the documented precedence rather than isolating consent as the cause diff --git a/wiki/platforms/tools/harness-mediated-tool-results.md b/wiki/platforms/tools/harness-mediated-tool-results.md index aa9c909..db81be7 100644 --- a/wiki/platforms/tools/harness-mediated-tool-results.md +++ b/wiki/platforms/tools/harness-mediated-tool-results.md @@ -7,7 +7,7 @@ confidence: verified sources: - https://code.claude.com/docs/en/hooks last_verified: 2026-08-05 -related: [platforms-shells-command-text-inspected-before-execution, platforms-processes-non-interactive-cli-invocation, infrastructure-agent-orchestration-control-signals-vs-primary-artifacts] +related: [platforms-shells-command-text-inspected-before-execution, platforms-processes-non-interactive-cli-invocation, infrastructure-agent-orchestration-control-signals-vs-primary-artifacts, platforms-tools-agent-permission-classifier-denials] --- # A Plugin Rewrites What a Tool Returns Before the Agent Sees It