Skip to content

feat(pilot): gate risky interact clicks with irreversible-action hook#1096

Merged
shaun0927 merged 2 commits into
developfrom
feat/1003-irreversible-confirmation
May 13, 2026
Merged

feat(pilot): gate risky interact clicks with irreversible-action hook#1096
shaun0927 merged 2 commits into
developfrom
feat/1003-irreversible-confirmation

Conversation

@shaun0927
Copy link
Copy Markdown
Owner

@shaun0927 shaun0927 commented May 12, 2026

Progress / Review status

Auto-refreshed 2026-05-13 — owner comments cleaned up to reduce review noise.

Field Value
Branch feat/1003-irreversible-confirmationdevelop
Draft no
CI ✅ all 9 checks passing
Mergeable ✅ MERGEABLE
Review decision
Codex (latest) 💡 suggestions posted
Other reviewers (latest) chatgpt-codex-connector: commented
Head ba1b6b4 — fix(pilot): match risk keywords on word boundaries
Commits 2

Owner comment cleanup: 1 issue + 0 inline review comments deleted. Outstanding feedback from automated/external reviewers above is unchanged.


Summary

  • Adds a deterministic browser-action risk classifier for irreversible click labels such as delete, payment, purchase, transfer, publish, and account/security changes.
  • Wraps interact AX/CSS click paths with the pilot contract runtime only when a critical action is detected and OPENCHROME_PILOT + OPENCHROME_CONTRACT_RUNTIME are enabled.
  • Keeps default OpenChrome interaction behavior pass-through when pilot contract runtime is disabled.

Direction / necessity check

Validation

  • npm test -- --runTestsByPath tests/harness/irreversible-action.test.ts
  • npm run build:src
  • npm run lint:tier

Post-merge live verification with OpenChrome

  1. Start OpenChrome without pilot contract flags and click a fixture button labeled Delete account; verify the click proceeds exactly as before.
  2. Start with OPENCHROME_PILOT=1 OPENCHROME_CONTRACT_RUNTIME=1 and register/enable a before-irreversible hook that denies critical actions.
  3. Click the same Delete account fixture through interact; verify the result is isError: true, includes _irreversibleAction.status, action evidence, and does not mutate the page.
  4. Click a safe negation label such as Cancel delete dialog; verify it is not gated.
  5. Click non-mutating hover on destructive text; verify it is not gated.

Closes #1003

@gemini-code-assist
Copy link
Copy Markdown

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@qodo-code-review
Copy link
Copy Markdown

ⓘ You've reached your Qodo monthly free-tier limit. Reviews pause until next month — upgrade your plan to continue now, or link your paid account if you already have one.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2d93d26b10

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/harness/irreversible-action.ts Outdated
export function classifyBrowserActionRisk(input: BrowserActionRiskInput): BrowserActionRisk {
const text = normalize(`${input.action} ${input.labelText ?? ''}`);
const evidence = CRITICAL_ACTION_WORDS.filter((word) => text.includes(word));
const negated = SAFE_NEGATIONS.some((word) => text.includes(word));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Avoid substring negation matches in risk classifier

The negation check uses text.includes(word) for entries like "view" and "read", so labels containing larger words such as "review and pay" are treated as safe ("review" contains "view") even when critical evidence ("pay") is present. In pilot+contract-runtime mode this bypasses the irreversible-action hook for common payment/submit phrasing, which defeats the new safety gate for real high-risk clicks.

Useful? React with 👍 / 👎.

Comment thread src/harness/irreversible-action.ts Outdated
const text = normalize(`${input.action} ${input.labelText ?? ''}`);
const evidence = CRITICAL_ACTION_WORDS.filter((word) => text.includes(word));
const negated = SAFE_NEGATIONS.some((word) => text.includes(word));
const mutatingClick = ['click', 'double_click'].includes(normalize(input.action));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Treat double_click as mutating after action normalization

normalize(input.action) converts underscores to spaces, but the mutating-action allowlist still checks for "double_click". As a result double_click becomes "double click" and never qualifies as mutating, so destructive double-click actions are never classified as critical and are not gated by the irreversible-action hook.

Useful? React with 👍 / 👎.

Irreversible browser actions need a deterministic hook point before mutation, but ordinary OpenChrome clicks must remain fast and unchanged. This guards only keyword-classified critical click labels and only activates the contract gate when the pilot runtime is explicitly enabled.

Constraint: Risky-action protection must not slow or alter normal OpenChrome interactions unless the pilot contract runtime is explicitly enabled.

Rejected: Prompt for confirmation on every click | too noisy and harmful for long-running automation.

Confidence: high

Scope-risk: moderate

Directive: Expand critical keyword coverage only with tests and keep default runtime pass-through behavior.

Tested: npm test -- --runTestsByPath tests/harness/irreversible-action.test.ts; npm run build:src; npm run lint:tier

Not-tested: Live irreversible-action denial against a real OpenChrome browser page.

Co-authored-by: OmX <omx@oh-my-codex.dev>
@shaun0927 shaun0927 force-pushed the feat/1003-irreversible-confirmation branch from 2d93d26 to 46c62b8 Compare May 13, 2026 09:18
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 46c62b8509

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/tools/interact.ts
Comment on lines +746 to +752
const axGuard = await guardIrreversibleBrowserAction(
{
toolName: 'interact',
action,
labelText: `${query} ${ax.role} ${ax.name}`,
pageUrl: page.url(),
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Guard ref/nodeRef click paths with irreversible-action hook

The new irreversible-action gate is only wired into the AX/CSS discovery branches, while the existing ref fast-path and nodeRef branch still execute page.mouse.click(...) directly. In pilot + contract-runtime mode, callers can bypass the safety hook by first resolving a destructive control and then invoking interact with ref/nodeRef, so high-risk actions like account deletion are not consistently blocked.

Useful? React with 👍 / 👎.

Comment thread src/tools/interact.ts
),
),
);
if (cssGuard.blocked) return cssGuard.blocked;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Ensure CSS tag cleanup runs on blocked guarded actions

When cssGuard.blocked is returned, the function exits before cleanupTags(page, DISCOVERY_TAG) runs, leaving __elDiscIdx markers from discoverElements in the DOM. Those stale markers are later read by getTaggedElementRect(...), so a subsequent interact can resolve the wrong element index and click the wrong target after a denied risky action.

Useful? React with 👍 / 👎.

Address Codex P1 + P2 review findings:
- P1: switch substring `includes` to a word-boundary regex match so
  negation tokens like "view"/"read" no longer mask critical evidence
  in phrases such as "Review and pay".
- P2: align the mutating-click allowlist with the post-normalize form
  so `double_click` (normalized to "double click") is still treated as
  mutating and gated by the irreversible-action hook.

Adds regression tests for both scenarios.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ba1b6b475b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/tools/interact.ts
{
toolName: 'interact',
action,
labelText: `${query} ${ax.role} ${ax.name}`,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Remove caller query text from risk-classification input

Including ${query} in labelText lets the caller suppress irreversible-action gating by adding any SAFE_NEGATIONS token (for example cancel, view, or read) to the query while still targeting a destructive control, because classifyBrowserActionRisk() treats any negation match as non-critical. This creates a straightforward bypass of the new safety hook in both AX and CSS interact paths whenever query text and element text are mixed.

Useful? React with 👍 / 👎.

Comment thread src/tools/interact.ts
),
),
);
if (cssGuard.blocked) return cssGuard.blocked;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Ensure discovery tags are cleaned before blocked return

Returning immediately on cssGuard.blocked skips cleanupTags(page, DISCOVERY_TAG), leaving discovery marker properties in the page DOM for blocked critical clicks. Since this path comes after discoverElements()/tagging and the function explicitly documents cleanup to avoid stale properties, blocked actions now regress into persistent DOM pollution that can interfere with later element discovery behavior.

Useful? React with 👍 / 👎.

@shaun0927 shaun0927 merged commit ab39c06 into develop May 13, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant