Repository navigation
Approval rules: pending Approvals, Custom Rules, Never-allowed actions (#989) - #999
Merged
Merged
Conversation
Amends ADR 0024; CONTEXT Approval updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… action Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One migration: `pending` review state, `never` verdict, a review's expiry time, the Custom Rules table (deleted with its user), and the `approval` Trigger source. The client gains the matching types, the Custom Rule type, the rules router's shapes and the `rule_approval` Rich control. Nothing uses them yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-review (#995) The rules router (list, create, update, delete: 50 active rules, 1-500 characters, another user's rule not found, a rule change in each of the user's Bots), the rules page at /settings/rules, the active rules in the reviewer's input with the modes' meanings in its policy (a hand-off rule reads as ask for now), and in each Bot's Prompt start with Change notes for edits. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…s lists (#995) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
When Auto-review blocks a shell command or a Connector tool call and the user doesn't answer within 15 seconds, the Approval goes `pending`, with an expiry 7 days after the block, and the Model gets a pending result naming the review instead of an error, so the Turn carries on (ADR 0030). - Stop during the wait still denies; a new Message (web or email) that interrupts the Turn leaves the row pending. - Closing a Turn's or Helper's waiting reviews never touches pending rows. - A General Helper's blocked shell call goes pending the same way; a Screen action (a Computer-use Helper's acting calls and always-ask checks) keeps the strict 15 seconds and expires. - A resumed wait whose row ever went pending reads as pending, so a late answer never runs the call inside the Turn. - The reviews list includes pending rows, and the Approval card shows "waiting · expires <date>" with Allow and Deny and no countdown; a waiting card past its deadline stays until the wait marks it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The pre-commit oxfmt run now passes a commit holding only files it ignores, such as a snapshot. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…roval (#996) The reviewer's verdict gains hand_off and never. Both record a row with no Approval and give the Model an error; never beats every rule. Its message always carries the Never-allowed list: the SDK's five classes, then the Host's from the new safety.neverAllowed option. rules.neverAllowed serves the list to a read-only section on the rules page, and a never review shows as a one-line divider in the thread. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ut duplicates (#996) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
An answer to a pending Approval now does what ADR 0030 says: - Allow marks the review `allowed` and queues one approved-call job, keyed by the review, in the same transaction. The job rebuilds the one tool the review names (`shell`, or the user's Connector tool) for the review's Bot, outside any Turn, and runs it with the stored input: no second review, the Quota checked first. A start marker means a crash never runs the call twice; its result then says it may have partly run. - The result is masked and cut like a Turn's, and wakes the Bot's Conversation as an Approval Trigger whose id comes from the review, so it lands once. A broken Connection, a spent Quota or a failed command is the result; a job that fails for good still wakes the Bot. - Deny marks it `denied` and wakes the Bot with the denial. - A Helper's pending call wakes its Bot, never the Helper. - A pending review past its expiry, or one already decided, refuses the answer. The shell call's body moves to `runShell`, shared by the loop and the job. The reviewer reads an Approval Trigger as the user's answer, never the call's result as their word, and a Turn an Approval woke gets no run divider in the thread. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…b runs (#993) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ever-allowed reviewer cases (#998) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… and the reviewer sees the answer (#992) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…D numbers sent off (#998) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
# Conflicts: # packages/botsdk/bot/src/bot/safety/review.ts
… call that never started (#992) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ies it (#997) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ewer line (#997) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- A call whose Approval went pending and was cut off by the user's Message ends the Turn with its pending result (R1). - An Allow or Deny just past the deadline, before the wait marks it pending, counts as a late answer and runs the call once; a Computer-use Helper's Screen action stays strict (R2). - The rules page saves a text edit and a mode pill clicked right after it (R3). - An approved call lists features with the Turn it came from, up to the tool's own (R5). - Computer failures and the rebuilt note share one helper, live and approved (R6). - One reason clause and one pending update for review and closing (R7). - The softened refusal rate leaves out reviewer failures on both sides (R8). - propose_rule allows one open proposal per Bot (R9) and records it in each of the user's Bots' threads (R11). - Contract comment fixed (R10). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A Turn re-queued by a crash and stopped by the user's Message keeps the call's pending result: the review reads on the replay's transaction. - The rules page puts the pill back and drops what waited when a save is refused, and its pills are off while a delete is in flight. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Summary
Implements the spec Approval rules, with all nine tickets, #990–#998. Before this, a blocked call failed after 15 seconds when the user wasn't looking. Now:
propose_rule. The user answers it on arule_approvalcard, and it applies only after their yes.rules.neverAllowedlists it).This also merges ADRs 0030, 0031 and 0032 from
docs/973-approval-rules.Closes #989
How it was tested
verify.shpassed on the integration tip (68391ba): format, lint, check-types, and vitest with 2491 passed and 34 skipped.--workers=2.rulechange reaches every Bot's thread live;openai:gpt-6.1-sol. It was 147/147 right, with one reviewer timeout scored as a block. A rerun of that case set was 49/49.Decisions I made
20261029113355_approval_rules. It adds thecustom_ruletable (two enums, withproposed_by_bot_idset null when that Bot is deleted), the review statepending, thebot_review.expires_atcolumn, the verdictneverand the Trigger sourceapproval. Enum values are only added, never removed. The owner approved it as specced.rulesrouter (list,create,update,delete,approve,deny,neverAllowed) and the reviews router's late answers. The owner chose to keep therules.neverAllowedroute.propose_ruleis refused until the user answers the first.oxfmtruns with--no-error-on-unmatched-pattern, so a snapshot-only commit passes the hook.propose_rule. The Bot finds it through the tool's description.Risk
🤖 Generated with Claude Code