fix: Gmail auth-resume failure — stop run-borking + restore persistent-approval grant - #5051
Conversation
A capability that failed with RuntimeFailureKind::Authorization (observed
when a Gmail extension activation failed authorization on auth-resume)
borked the entire run with a misleading "execution driver was temporarily
unavailable" message instead of surfacing a clean, model-visible denial.
Root cause: runtime_model_visible_failure_to_loop built the denied
outcome's reason_kind from RuntimeFailureKind::as_str(), which for the
Authorization variant is the literal string "authorization". The
loop-safe identifier validator rejects "authorization" as a sensitive
marker (it guards against leaking Authorization: header material), so
CapabilityDeniedReasonKind::unknown("authorization") failed, producing an
internal "capability denied reason kind could not be represented" error.
The executor mapped that to HostUnavailable { stage: Capability } and the
planned driver recorded a terminal driver-unavailable failure.
Fix: map Authorization/PolicyDenied runtime failures to leak-safe denied
reason tags ("auth_denied"/"policy_denied") via denied_reason_kind_for()
instead of passing the raw kind string through the identifier validator.
The denial now surfaces to the model as a normal Denied outcome and the
run continues.
Adds an Authorization regression case to
runtime_failure_to_loop_honors_model_visible_disposition (the existing
test only covered PolicyDenied, which validates cleanly and so missed
this).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A capability authorized only by a persistent-approval grant (e.g. extension_activate under admin-config FirstParty trust, as in the reborn-cli local-dev runtime) failed authorization on the credential auth-resume path, even though the initial dispatch was authorized. The initial dispatch injects the persistent-approval grant into the execution context via apply_persistent_approval_policy before authorizing (production.rs dispatch_capability / spawn_capability). When the handler then raised AuthRequired (missing credential), the run was parked as BlockedAuth and a credential gate opened. On resume, auth_resume_capability rebuilt the context WITHOUT re-applying the persistent-approval policy, so dispatch_resumed_capability re-authorized a grant-less context and returned AuthorizationDenied. With approval_request_id = None there is no approval lease to carry a grant, so the persistent policy is the only authority — and it was dropped. Observed connecting Gmail: OAuth completed, but the extension_activate auth-resume failed with an authorization error; a later fresh dispatch (which re-injects the grant) succeeded. Combined with the separate denied-reason representation bug, the failure surfaced as a misleading "execution driver was temporarily unavailable". Fix: auth_resume_capability now calls apply_persistent_approval_policy (action = Dispatch) before building the resume request, mirroring dispatch_capability. The helper is a no-op when no matching policy/grant exists, so capabilities requiring fresh approval are unaffected. resume_spawn_capability is intentionally not changed: it only handles BlockedApproval resumes, which always carry an approval_request_id and claim a fingerprinted approval lease that injects the grant — so it does not have this gap. Adds default_runtime_uses_persistent_policy_as_auth_resume_authority to the persistent-approvals contract, which previously covered only the dispatch and spawn authority paths. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
🚅 Deployed to the ironclaw-pr-5051 environment in ironclaw-ci-preview
|
There was a problem hiding this comment.
Code Review
This pull request addresses two key issues: it ensures persistent-approval grants are re-applied during auth-resume preflights, and it maps authorization and policy runtime failures to leak-safe identifiers ('auth_denied' and 'policy_denied') to prevent internal errors caused by sensitive marker validation. Regression tests have been added to verify both fixes. I have no further feedback to provide as the changes are well-implemented and covered by tests.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
📝 WalkthroughSummary by CodeRabbitRelease Notes
Walkthrough
ChangesPersistent-approval re-injection on auth-resume + safe denial mapping
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
henrypark133
left a comment
There was a problem hiding this comment.
Code Review (multi-agent)
Intent: Fix Gmail auth-resume so persistent approval is reapplied and authorization denials surface cleanly instead of failing the run.
Stats: 0 findings (from 0 raw, 0 after dedup) across 0 files. Reviewers run: security, bugs, performance, tests, conventions, local-patterns, maintainability, approach. Reviewers failed: none. Body-only: 0
No actionable findings from the multi-agent review.
…t-approval grant (nearai#5051) * fix(loop): stop authorization denials from borking the run A capability that failed with RuntimeFailureKind::Authorization (observed when a Gmail extension activation failed authorization on auth-resume) borked the entire run with a misleading "execution driver was temporarily unavailable" message instead of surfacing a clean, model-visible denial. Root cause: runtime_model_visible_failure_to_loop built the denied outcome's reason_kind from RuntimeFailureKind::as_str(), which for the Authorization variant is the literal string "authorization". The loop-safe identifier validator rejects "authorization" as a sensitive marker (it guards against leaking Authorization: header material), so CapabilityDeniedReasonKind::unknown("authorization") failed, producing an internal "capability denied reason kind could not be represented" error. The executor mapped that to HostUnavailable { stage: Capability } and the planned driver recorded a terminal driver-unavailable failure. Fix: map Authorization/PolicyDenied runtime failures to leak-safe denied reason tags ("auth_denied"/"policy_denied") via denied_reason_kind_for() instead of passing the raw kind string through the identifier validator. The denial now surfaces to the model as a normal Denied outcome and the run continues. Adds an Authorization regression case to runtime_failure_to_loop_honors_model_visible_disposition (the existing test only covered PolicyDenied, which validates cleanly and so missed this). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(host-runtime): re-apply persistent-approval grant on auth-resume A capability authorized only by a persistent-approval grant (e.g. extension_activate under admin-config FirstParty trust, as in the reborn-cli local-dev runtime) failed authorization on the credential auth-resume path, even though the initial dispatch was authorized. The initial dispatch injects the persistent-approval grant into the execution context via apply_persistent_approval_policy before authorizing (production.rs dispatch_capability / spawn_capability). When the handler then raised AuthRequired (missing credential), the run was parked as BlockedAuth and a credential gate opened. On resume, auth_resume_capability rebuilt the context WITHOUT re-applying the persistent-approval policy, so dispatch_resumed_capability re-authorized a grant-less context and returned AuthorizationDenied. With approval_request_id = None there is no approval lease to carry a grant, so the persistent policy is the only authority — and it was dropped. Observed connecting Gmail: OAuth completed, but the extension_activate auth-resume failed with an authorization error; a later fresh dispatch (which re-injects the grant) succeeded. Combined with the separate denied-reason representation bug, the failure surfaced as a misleading "execution driver was temporarily unavailable". Fix: auth_resume_capability now calls apply_persistent_approval_policy (action = Dispatch) before building the resume request, mirroring dispatch_capability. The helper is a no-op when no matching policy/grant exists, so capabilities requiring fresh approval are unaffected. resume_spawn_capability is intentionally not changed: it only handles BlockedApproval resumes, which always carry an approval_request_id and claim a fingerprinted approval lease that injects the grant — so it does not have this gap. Adds default_runtime_uses_persistent_policy_as_auth_resume_authority to the persistent-approvals contract, which previously covered only the dispatch and spawn authority paths. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Problem
Connecting Gmail failed with a misleading "the execution driver was temporarily unavailable" error, even though OAuth completed successfully (and the account connected on a later retry). Two distinct defects compounded:
Root cause
In the
reborn-cli/ admin-config FirstParty local-dev runtime,extension_activateis authorized only by a persistent-approval grant. The initial dispatch injects that grant (apply_persistent_approval_policy) before authorizing; the handler then raisesAuthRequired(missing Google token) and the run parks asBlockedAuthwith a credential gate.On resume,
auth_resume_capabilityrebuilt the execution context without re-applying the persistent-approval policy. Withapproval_request_id = Nonethere is no approval lease to carry a grant, sodispatch_resumed_capabilityre-authorized a grant-less context →AuthorizationDenied. The denial then hit a second bug: the denied reason-kind was built fromRuntimeFailureKind::Authorization.as_str()="authorization", which the loop-safe identifier validator rejects as a sensitive marker → internal "could not be represented" error → mapped toHostUnavailable→ terminal driver-unavailable failure.Fixes
fix(loop)— stop authorization denials from borking the runMap
Authorization/PolicyDeniedruntime failures to leak-safe denied reason tags (auth_denied/policy_denied) viadenied_reason_kind_for()instead of passing the raw kind string through the identifier validator. Authorization denials now surface to the model as a cleanDeniedoutcome and the run continues.fix(host-runtime)— re-apply persistent-approval grant on auth-resumeauth_resume_capabilitynow callsapply_persistent_approval_policy(action =Dispatch) before building the resume request, mirroringdispatch_capability. The helper is a no-op when no matching policy/grant exists, so capabilities requiring fresh approval are unaffected.resume_spawn_capabilityis intentionally not changed: it only handlesBlockedApprovalresumes, which always carry anapproval_request_idand claim a fingerprinted approval lease that injects the grant — so it does not have this gap.Tests
runtime_failure_to_loop_honors_model_visible_disposition— added anAuthorizationcase (existing coverage wasPolicyDenied-only, which validates cleanly and missed the bug).default_runtime_uses_persistent_policy_as_auth_resume_authority— drivesauth_resume_capabilityend-to-end against aBlockedAuthrun authorized only by a persistent grant; asserts the run completes (fails pre-fix). The persistent-approvals contract previously covered only dispatch and spawn authority.Verification
host_runtime_persistent_approvals_contract: 11/11 pass.ironclaw_loop_supportlibruntime_failure_to_looptests pass.cargo clippy -p ironclaw_host_runtime --tests --all-features: clean.cargo fmt --all --check: clean.Scope note: verification covered the two affected packages and their tests, not a full-workspace gate.
🤖 Generated with Claude Code