2.1.0: execute_action and execute_method, with a forced permission prompt for irreversible tools - #53
Merged
Merged
Conversation
…guard
Brass-Monkey could read server actions but never run them, and had no way to
press a workflow button — so an agent wanting to confirm a sales order had to
fake it by writing the `state` field, skipping every side effect Odoo attaches
to the real transition.
Two tools, split deliberately rather than merged. Model methods are declared by
the model and can be validated against live views before the call; server
actions are mutable data rows that can change between introspection and
execution. Different validation models, different tools.
execute_action (ir.actions.server):
- expands the whole child_ids tree and classifies on the union, so a `multi`
wrapper containing one Python step is treated as Python
- blocks code/webhook/mail_post/sms behind acknowledge_unsafe — arbitrary
execution and outbound egress to third parties
- snapshots only the declarative states, and records why when it cannot
- returns any follow-up action as inert data; never chains
execute_method (workflow buttons):
- hard, non-overridable deny-list on ORM primitives, so it cannot become a
bypass around the guards on write_record/create_record/unlink_record
- action_/button_/toggle_ naming convention, else acknowledge_unsafe
- verifies the method is bound to a <button type="object"> in the combined
view arch, which covers Studio-added buttons too
- snapshots workflow state markers around the call
Shared ExecutionGuard owns justification, target resolution, snapshot capture
and the triple audit write, so the two tools cannot drift apart on safety.
Also closes a hole the new tools would otherwise have: acknowledge_unsafe
assumes a human reads the refusal, but Claude Code's auto and bypassPermissions
modes remove the prompt entirely, letting the agent retry its own refusal with
the flag set. unlink_record and execute_action now advertise
_meta["anthropic/requiresUserInteraction"], which forces the prompt in every
permission mode. Deliberately not set on the routine write tools, which would
make auto mode unusable. Limits are documented in docs/execution-safety.md.
Version synced to 2.1.0 across the four manifests; package-lock had drifted
from package.json on both version and the engines block added by the dependency
roundup, and is now regenerated.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WHB8rQL4x7Vog8D1sret5X
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.
What this changes
Adds two tools —
execute_action(run anir.actions.server) andexecute_method(press a workflow button) — behind a sharedExecutionGuard, and makesunlink_recordandexecute_actionprompt a human on every call regardless of Claude Code's permission mode.Why
Brass-Monkey could read server actions but never run them, and had no way to press a workflow button. An agent wanting to confirm a sales order had to fake it by writing the
statefield — which sets a string and skips every side effect Odoo attaches to the real transition, leaving the database inconsistent.The two tools are split deliberately rather than merged. Model methods are declared by the model and can be validated against live views before the call; server actions are mutable data rows that can change between introspection and execution. Different validation models, different tools.
execute_actionchild_idstree and classifies on the union, so amultiwrapper containing one Python step is treated as Python.code/webhook/mail_post/smsbehindacknowledge_unsafe— arbitrary execution, and outbound egress to third parties.execute_methodwrite_record/create_record/unlink_record.action_/button_/toggle_naming convention; anything else needsacknowledge_unsafe.<button type="object">in the combined view arch, which covers Studio-added buttons.The permission-mode hole this also closes
acknowledge_unsafeassumes a human reads the refusal. Claude Code'sautoandbypassPermissionsmodes remove the prompt entirely, so the agent could read its own refusal and immediately retry with the flag set — nobody ever sees the Python.unlink_recordandexecute_actionnow advertise_meta["anthropic/requiresUserInteraction"], which forces the prompt in every permission mode and suppresses "don't ask again".Deliberately not set on
write_record,create_recordorexecute_method: it prompts on every call, and applying it to routine data work would make auto mode unusable. Limits — Claude Code only, v2.1.199+, static, and headless--permission-prompt-toolconverts allow to deny — are written up indocs/execution-safety.md.How it was verified
npm testpasses locally — 98 tests, 16 filesnpm run buildsucceeds; bundle regeneratedtools/listexercised over stdio against the built bundle: 25 tools,requiresUserInteractionpresent on exactlyunlink_recordandexecute_actionrun→_runfallback and the view-arch regex are precisely the parts mocks cannot validate. Needs an MCP Inspector pass before tagging.skills/guidance updated to match (odoo-dev,odoo-data-ops, newdocs/execution-safety.md)Checklist
dist/is included — same-repo maintainer release path, whichcontributor-guard.ymlexplicitly permitssrc/tools/schemas.tsand co-located Zodjustificationand write to all three audit channelstests/execution-tools.test.ts(14 cases) plus_metaassertions in the integration testAlso in this change
package-lock.jsonhad drifted frompackage.jsonon two counts: the version (still 2.0.1) and theenginesblock added by the dependency roundup. Regenerated. Worth notingscripts/sync-version.mjsdoes not cover the lockfile — a gap that will recur next release.Separately, GitHub is now reporting 2 vulnerabilities on
main(1 high, 1 low). Unrelated to this PR, but worth a look.