Tell plain-launch sessions their model calls are not priced or policy-checked (0.19.0) - #37
Merged
Merged
Conversation
…-checked (0.19.0) Production 2026-09-16: 0 of 69 external tenants have a single llm.proxy.* row, and every external harness tenant has zero costed runs. Everyone runs the plain binary; nobody runs <harness>-acp, and nothing tells them — the only surface that mentions cost is the launcher's own exit line, which only prints after you already used the launcher. The hook runs under the plain binary, so the hook says it. Port of the installer's bundled-engine change (agenticcontrolplane.com PR #178) into the govern.mjs that online installs fetch and the marketplace plugin runs — #178 alone reaches nobody in today's data. One systemMessage, once per session, on the first ALLOWED call: [ACP] Tool calls in this session are checked and logged. Model calls are not: plain `claude` sends them straight to the provider, so they are neither priced nor policy-checked (tool-result redaction, model routing). For the cost X-ray and model-call policy, launch with `claude-acp` (~/.acp/bin/claude-acp). Shown once per session. - Detection: every launcher is generated from one template whose first act is `export ACP_KEY`; the hook inherits that env. No ACP_KEY, and no provider base URL at the ACP proxy, means the session was not started by a launcher. Bias to silence on doubt. - Once per session: marker ~/.acp/session-notices/unpriced-<session_id> (id sanitised to [A-Za-z0-9._-], capped at 120), created with an exclusive flag so parallel hook processes for the same session cannot both fire. No session_id in the payload means silence, no marker. Markers older than 7 days are pruned; the directory is capped at 200. - Every harness with a launcher is named by ACP_CLIENT (claude-acp, codex-acp for both "codex" and "codex-plugin", qwen-acp, opencode-acp, pi-acp, hermes-acp, prime-acp, grok-acp, dsh-acp). Cursor has no launcher and gets nothing. - Marketplace-only installs (`claude plugin install`) have this hook but no launcher on disk; for them the notice names the installer instead of a path that does not exist. One deviation from #178, in favour of saying something true. - Cloud mode only: the local path never reaches it. No-credential and managed-unenrolled paths never reach it either. - Purely additive: rides the allow path as one JSON object (merged with the tier notice and the wire warning when they apply — the updatedInput branch now reads the message once instead of twice), never a deny or an ask, and the whole thing is wrapped so any failure is silence. - Version 0.19.0 so the attestation hash is compared against a fresh version. Branched off main independently of the transcript-usage PR (0.18.0); the two conflict in bin/govern.mjs and this one rebases. Tests (test/unpriced-session-notice.test.mjs, real hook against a stub gateway): notice on a plain launch with nothing that could change the call; silent on the 2nd/3rd call of the same session; fires once for a new session; nothing without a session id; nothing with ACP_KEY in env or a provider base URL at the proxy; deny and ask byte-identical with no notice, then the notice rides the session's first allowed call; wire warning + notice as ONE object; codex names codex-acp, cursor gets nothing; six parallel processes yield exactly one notice; stale markers pruned and the directory capped; hostile session ids sanitised; launcher absent names the installer; --local silent. wire-warning.test.mjs now runs as a launched session so its assertions stay about the warning.
…-notice # Conflicts: # .claude-plugin/marketplace.json # README.md # bin/govern.mjs # plugin.json
…licy half it lacks #36 made plain `claude` priced — from the transcript, at API-rate equivalents — so "neither priced nor policy-checked" became false the moment it merged. Rewritten to lead with what the session has and name only what still needs the launcher: [ACP] Tool calls in this session are checked and logged, and model-call cost is estimated from the session transcript (API-rate equivalent, not a metered charge). Model calls are not policy-checked: plain `claude` sends them straight to the provider, so tool-result redaction and model routing are off. For those, launch with `claude-acp` (~/.acp/bin/claude-acp). Shown once per session. The estimate-vs-metered distinction is stated up front so it does not arrive later as a support question. The wording is chosen by the same condition that makes the session priced — a readable transcript_path on the payload — so a harness that hands over no transcript still gets the plainer "neither priced nor policy-checked" form rather than a claim the hook cannot back. Every mechanical guarantee is unchanged: allow path only, once per session via the exclusive marker, silence with no session_id, never in --local, never with ACP_KEY or a proxy base URL. Tests: wording assertions updated; new case for a payload with no (or a missing) transcript; transcript-model-usage.test.mjs runs as a launched session so its PreToolUse assertion stays about the body. 220/220.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Production 2026-09-16: 0 of 69 external tenants have ever used a
<harness>-acplauncher. Everyone runs plainclaude; nothing tells them what that session does and does not get from ACP.agenticcontrolplane.com#178 added a notice to the installer's bundled offline engines — which online installs never run, and which the marketplace plugin never touches. This is the port into the
govern.mjsthat actually executes. Rebased onto #36 (merged): with the transcript collector in, plainclaudeis priced, so the message leads with what the session has and names only the policy half as missing.What
One
systemMessage, once per session, on the first allowed call of a plain-launch session:transcript_path— the same condition under which Send transcript model usage on PostToolUse so plainclaudesessions get costed (0.18.0) #36 prices the session. A harness that hands over no transcript (nothing priced) gets the plainer "…neither priced nor policy-checked…" form instead. Never a claim the hook cannot back.export ACP_KEY; the hook inherits that env. NoACP_KEY, and noANTHROPIC_BASE_URL/OPENAI_BASE_URLatagenticcontrolplane.com, means the session was not started by a launcher. Bias to silence on doubt.~/.acp/session-notices/unpriced-<session_id>(id sanitised to[A-Za-z0-9._-], capped at 120), created with{flag:"wx"}so parallel hook processes for one session cannot both fire. Nosession_idin the payload → silence, no marker. Markers older than 7 days are pruned; the directory is capped at 200 newest.ACP_CLIENT(claude-acp;codex-acpfor bothcodexand the Codex plugin'scodex-plugin;qwen-acp,opencode-acp,pi-acp,hermes-acp,prime-acp,grok-acp,dsh-acp). Cursor has no launcher and gets nothing.claude plugin install): the notice ends withinstall the \claude-acp` launcher: curl -sf https://agenticcontrolplane.com/install.sh | bash` instead of a path that does not exist.updatedInputbranch now reads the message once, not twice — with an exclusive-create claim a second read would have dropped it). Wrapped in try/catch: any failure is silence.Tests
node --test— 220 pass, 0 fail (16 intest/unpriced-session-notice.test.mjs, spawning the real hook against a stub gateway):hookSpecificOutput; never says "governance"ACP_KEYin env; nothing with a provider base URL at the proxycodex-acp; cursor gets nothing--localmode silent, no marker dir, call still audited locallywire-warning.test.mjsandtranscript-model-usage.test.mjsrun as launched sessions (ACP_KEYin env) so their assertions stay about the warning / the PostToolUse body alone.Not merged, not published to npm, not deployed. Publishing is yours.