Skip to content

fix: honour a per-call approval decision made after a sticky one - #4430

Closed
chiruu12 wants to merge 4 commits into
openai:mainfrom
chiruu12:fix/per-call-approval-after-sticky
Closed

chiruu12 wants to merge 4 commits into
openai:mainfrom
chiruu12:fix/per-call-approval-after-sticky

Conversation

@chiruu12

Copy link
Copy Markdown
Contributor

Summary

Sticky and per-call approval decisions share the same two fields on _ApprovalRecord but use different shapes: always_approve and always_reject store a bool, while a per-call decision stores call IDs in a list. Once a sticky decision existed, a later per-call decision on the same tool was lost.

Rejecting one call after always_approve was ignored on read. get_approval_status returned at approval_entry.approved is True before reaching rejected_ids, so the rejection and its message were stored but never read, and the tool ran. In the other direction, always_reject leaves the bool False in approved, so the writer's isinstance(target, list) guard was false and a later approve_tool was dropped before it reached state.

Rejecting a single call from the interruption list after approving a tool for the session is a normal human in the loop flow, so this reads as a safety issue rather than a cosmetic one.

The reader now checks a decision recorded for the exact call before falling back to the sticky value, and the writer starts a list when the field still holds a bool. Sticky behaviour for every other call is unchanged, and always_approve followed by always_reject still resolves to False.

Test plan

Added test_run_context_per_call_decision_overrides_sticky_decision, which covers both directions and asserts that other calls keep following the sticky decision. It fails on main and passes here.

.agents/skills/code-change-verification/scripts/run.sh passes: format, lint, typecheck and the full test suite.

Issue number

Closes #4429

Checks

  • I've added new tests, if relevant
  • I've run .agents/skills/code-change-verification/scripts/run.sh
  • I've confirmed all verification steps pass
  • If using Codex, I've run /review before submitting this PR

Copilot AI lite review requested due to automatic review settings August 15, 2026 03:28

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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

Copy link
Copy Markdown

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: c5cac76f02

ℹ️ 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/agents/run_context.py Resolved
Comment thread src/agents/run_context.py Resolved

@seratch seratch left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for addressing the sticky-state regression and rejection-message handling. I rechecked the latest head, and the approval transition logic now behaves correctly in both directions.

One compatibility blocker remains. A per-call approval after always_reject is serialized as schema 1.15 with approved=[call_id] and rejected=true. The released v0.20.0 reader accepts schema 1.15 but resolves that call as rejected, so a snapshot written by the updated SDK can be silently mis-resumed by the released reader. Please bump CURRENT_SCHEMA_VERSION, add its summary, and cover backward reading of an existing 1.15 mixed record plus new-schema round-trip and resumed behavior. Please also add explicit same-call exception-reversal tests in both directions.

Once those focused changes and CI are complete, this should be ready for another review.

@chiruu12

Copy link
Copy Markdown
Contributor Author

Both fixed.

The first was a regression I introduced: a per-call decision that matched the sticky one replaced the bool with a list, so other calls lost the sticky answer. It now only converts when the new decision opposes the sticky value.

The second skips the sticky message for a call in the approved list. Other calls still get it.

Tests cover both. Verification is green.

@chiruu12

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 👍

Reviewed commit: 4f20ffaff6

ℹ️ 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".

@chiruu12
chiruu12 requested a review from seratch August 15, 2026 05:21

@seratch seratch left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for the update. I refreshed the latest remote head and checks. All 14 checks are green and the branch is cleanly mergeable, but the head is still 4f20ffaff620 and does not include the requested RunState schema changes or durable-state tests.

The existing changes-requested review therefore remains unresolved. Please bump the schema version, add the schema summary, cover backward reading of the existing 1.15 mixed approval record, and test new-schema round-trip and resumed behavior. Please also add the explicit same-call exception-reversal cases in both directions. Once those changes are pushed and CI passes again, this should be ready for another review.

I may implement a complete solution w/ your contribution credit later.

@chiruu12

Copy link
Copy Markdown
Contributor Author

Pushed. Bumped to 1.16 with a summary, gated the override on _PER_CALL_APPROVAL_OVERRIDE_MIN_SCHEMA_VERSION so a 1.15 snapshot still resolves as sticky, and added round-trip, backward-read and same-call reversal tests in both directions. Also moved 1.15 into the released list in test_supported_schema_versions_match_released_boundary.

One test is still red and I did not want to guess at it. test_historical_fixture_corpus_matches_supported_schema_versions wants versions and features entries for 1.16, but those reference the commit that introduced the version, and 1.16 has none until this merges. canonical_compatibility does not fit either, since it relabels a later writer's payload and there is no later writer. How would you like that handled?

@chiruu12

Copy link
Copy Markdown
Contributor Author

Separate question while I was checking the compatibility direction. Released 0.20.0 writes approved=true, rejected=[call_id] itself when a call is rejected after always_approve, and its reader resolves that as approved, so the tool runs after a resume even though the call was rejected. That looks like the same fail-open surviving into existing 1.15 snapshots rather than something this PR introduces. Does that match your read, and is it in scope here or better as its own issue?

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

Copy link
Copy Markdown

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: ecbedbfcde

ℹ️ 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/agents/run_state.py
"Persists canonical tool invocation identity plus sanitized mount authority and trusted "
"rebind metadata, durable pending input, and resumable next-model-call state."
),
"1.16": "Lets a per-call approval decision override a sticky one for the same tool.",

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 Add the 1.16 schema fixtures to the compatibility corpus

Because SUPPORTED_SCHEMA_VERSIONS is derived from SCHEMA_VERSION_SUMMARIES, adding 1.16 here makes the compatibility corpus test expect matching versions and features entries for 1.16; tests/fixtures/run_state/sources.json still only lists schemas through 1.15, so test_historical_fixture_corpus_matches_supported_schema_versions will fail whenever that test module runs. Please add the minimal/feature fixtures and source metadata for the new durable schema version, or avoid advertising 1.16 as supported until the corpus is updated.

Useful? React with 👍 / 👎.

Comment thread src/agents/run_state.py
Comment on lines +3350 to +3353
context._rebuild_approvals(
context_data.get("approvals", {}),
per_call_overrides_sticky=(schema_major, schema_minor) >= (per_call_major, per_call_minor),
)

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 Apply the legacy override rule to hosted MCP approvals

This compatibility switch only rebuilds the string-key context["approvals"] records with pre-1.16 sticky precedence; immediately below, hosted_mcp_approvals are restored via _rebuild_hosted_mcp_approvals() which still calls _restore_approval_record() with the default new per-call precedence. A schema 1.14/1.15 snapshot containing a hosted MCP sticky server-tool approval plus a per-call rejection was previously resolved as sticky-approved, but after this change it resumes as rejected for that request, so old durable snapshots can change authorization behavior. Please thread the same per_call_overrides_sticky flag through hosted MCP approval restoration as well.

AGENTS.md reference: AGENTS.md:L168-L168

Useful? React with 👍 / 👎.

@seratch

seratch commented Aug 15, 2026

Copy link
Copy Markdown
Member

Thanks for reporting the issue. #4447 resolved it and its commit includes you as a co-author. Thanks again for your time and efforts here.

@seratch seratch closed this Aug 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Rejecting a single call after always_approve is ignored, so the tool still runs

3 participants