Skip to content

fix(google-wasm): auth required errors - #4969

Merged
serrrfirat merged 6 commits into
mainfrom
codex/google-wasm-auth-required
Jun 23, 2026
Merged

serrrfirat merged 6 commits into
mainfrom
codex/google-wasm-auth-required

Conversation

@serrrfirat

Copy link
Copy Markdown
Collaborator

Summary

  • return structured auth_required guest errors for Google API 401 responses in bundled Drive, Docs, Sheets, and Slides WASM tools
  • rebuild the affected first-party Google WASM artifacts
  • add a host-runtime regression test proving a Drive 401 becomes RuntimeCapabilityOutcome::AuthRequired

Root Cause

The bundled Google WASM tools returned raw provider error strings for non-2xx Google API responses. For 401 responses, that raw text was mapped by the host as operation_failed, which made the model-visible recovery say same-call retry was allowed instead of triggering the auth gate.

The host runtime already supports structured WASM guest errors with kind = auth_required; the Google tools were just not emitting that structured shape.

Validation

  • cargo fmt --check
  • ./scripts/build-wasm-extensions.sh --first-party
  • cargo test -p ironclaw_host_runtime --test github_wasm_runtime_contract host_runtime_services_maps_google_drive_wasm_401_to_auth_required
  • cargo test -p ironclaw_host_runtime --test github_wasm_runtime_contract host_runtime_services_routes_google
  • git diff --check

Notes

I cleaned local generated build directories after the WASM rebuild to recover disk space, then reran the focused host-runtime tests successfully.

@coderabbitai

coderabbitai Bot commented Jun 16, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0de38844-bedb-4148-8e74-e03a324fe6a9

📥 Commits

Reviewing files that changed from the base of the PR and between 02c6ed0 and bb5cdd7.

📒 Files selected for processing (1)
  • crates/ironclaw_host_runtime/tests/github_wasm_runtime_contract.rs

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes
    • Improved Google API error handling across Docs, Drive, Sheets, and Slides.
    • HTTP 401 “auth required” responses now produce structured, consistent error details, while other non-2xx responses are formatted uniformly with service/status/body information.
  • Tests
    • Added runtime contract tests verifying HTTP 401 triggers AuthRequired for Drive file listing and uploads.
    • Enhanced the network egress test double to simulate specific HTTP status codes and bodies.

Walkthrough

Four Google WASM extension crates (google-docs, google-drive, google-sheets, google-slides) each gain an api_status_error helper and GOOGLE_API_AUTH_REQUIRED_ERROR constant; HTTP 401 now returns a JSON-encoded {code, kind: "auth_required"} payload instead of a plain string. A host-runtime integration test verifies the AuthRequired capability outcome end-to-end for Drive.

Changes

Structured 401 auth-required errors across Google WASM API modules

Layer / File(s) Summary
api_status_error helper and constant
crates/ironclaw_first_party_extensions/assets/google-docs/wasm-src/src/api.rs, crates/ironclaw_first_party_extensions/assets/google-drive/wasm-src/src/api.rs, crates/ironclaw_first_party_extensions/assets/google-sheets/wasm-src/src/api.rs, crates/ironclaw_first_party_extensions/assets/google-slides/wasm-src/src/api.rs
Adds GOOGLE_API_AUTH_REQUIRED_ERROR constant and api_status_error(service, status, body) in each module. The helper returns a JSON object with code and kind: "auth_required" for HTTP 401; for other statuses, returns {service} API returned status {status}: {body_text}.
Wire api_status_error into API call paths
crates/ironclaw_first_party_extensions/assets/google-docs/wasm-src/src/api.rs, crates/ironclaw_first_party_extensions/assets/google-drive/wasm-src/src/api.rs, crates/ironclaw_first_party_extensions/assets/google-sheets/wasm-src/src/api.rs, crates/ironclaw_first_party_extensions/assets/google-slides/wasm-src/src/api.rs
Routes non-2xx error handling in api_call and (in Drive) api_call_raw through the new api_status_error helper instead of inline error construction. Updates Drive upload_file to use the same helper for consistency.
Test infrastructure and Drive 401 integration test
crates/ironclaw_host_runtime/tests/github_wasm_runtime_contract.rs
Adds status: u16 field to RecordingNetworkHttpEgress test double, introduces with_status_body(status, body) constructor, updates with_body to default to 200, and modifies execute to populate response status from stored field. New tests simulate Drive 401 responses for list and upload operations, inject expired tokens, and assert RuntimeCapabilityOutcome::AuthRequired.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related issues

Possibly related PRs

  • nearai/ironclaw#4968: Both PRs classify Google "UNAUTHENTICATED"/HTTP 401 failures as AuthRequired outcome (this PR via structured 401 error payloads and host-runtime tests, that PR via GSuite refresh-retry error classification).

Poem

Four API crates, one pattern aligned,
A 401 now returns JSON, refined—
auth_required in kind, the code held tight,
The test double gains status, the egress shines bright,
No plain string survives where structured errors lead! 🦀

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed Title follows Conventional Commits style with type(scope): summary format and accurately describes the core change: structured auth_required error handling for Google API 401 responses.
Description check ✅ Passed Description covers all required template sections: Summary (3 bullets), Change Type (Bug fix checked), Linked Issue, comprehensive Validation checklist with specific test commands, Security Impact (auth gate triggering), and Blast Radius.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added size: M 50-199 changed lines risk: low Changes to docs, tests, or low-risk modules contributor: core 20+ merged PRs labels Jun 16, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces structured error handling for 401 Unauthorized responses across Google Docs, Drive, Sheets, and Slides WASM extensions, mapping them to a structured auth_required JSON error. It also adds a corresponding integration test in the host runtime to verify this behavior. Feedback suggests updating the upload_file function in the Google Drive extension to also use the new api_status_error helper, ensuring consistent authentication gate triggering during file uploads.

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.

@serrrfirat
serrrfirat marked this pull request as ready for review June 16, 2026 10:19

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@crates/ironclaw_first_party_extensions/assets/google-drive/wasm-src/src/api.rs`:
- Line 40: In
crates/ironclaw_first_party_extensions/assets/google-drive/wasm-src/src/api.rs,
the upload_file function at lines 60 and 66-76 currently builds plain-text
errors instead of routing through api_status_error like the code at line 40
does. Replace the error construction at those locations (siblings) with calls to
api_status_error to ensure 401 responses in multipart uploads are properly
classified as AuthRequired rather than generic failures. Additionally, add a
regression test (using #[test] or #[tokio::test]) that verifies
google-drive.upload_file correctly handles a 401 response through the
host-runtime caller, confirming the auth_required path is taken for
authentication failures on multipart uploads.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 82cb32b8-29f4-4222-9716-7d613b17e16b

📥 Commits

Reviewing files that changed from the base of the PR and between 194a81a and 3cd9134.

⛔ Files ignored due to path filters (4)
  • crates/ironclaw_first_party_extensions/assets/google-docs/wasm/google_docs_tool.wasm is excluded by !**/*.wasm, !**/*.wasm
  • crates/ironclaw_first_party_extensions/assets/google-drive/wasm/google_drive_tool.wasm is excluded by !**/*.wasm, !**/*.wasm
  • crates/ironclaw_first_party_extensions/assets/google-sheets/wasm/google_sheets_tool.wasm is excluded by !**/*.wasm, !**/*.wasm
  • crates/ironclaw_first_party_extensions/assets/google-slides/wasm/google_slides_tool.wasm is excluded by !**/*.wasm, !**/*.wasm
📒 Files selected for processing (5)
  • crates/ironclaw_first_party_extensions/assets/google-docs/wasm-src/src/api.rs
  • crates/ironclaw_first_party_extensions/assets/google-drive/wasm-src/src/api.rs
  • crates/ironclaw_first_party_extensions/assets/google-sheets/wasm-src/src/api.rs
  • crates/ironclaw_first_party_extensions/assets/google-slides/wasm-src/src/api.rs
  • crates/ironclaw_host_runtime/tests/github_wasm_runtime_contract.rs

@serrrfirat serrrfirat changed the title [codex] fix google wasm auth required errors fix(google-wasm): auth required errors Jun 17, 2026
@serrrfirat

Copy link
Copy Markdown
Collaborator Author

Addressed the review comments in 3d55f30bbd8a.

Fixed Review Feedback

Reviewer Direct quote Fix
gemini-code-assist “will not return the structured auth_required error” Updated google-drive.upload_file to route non-2xx multipart upload responses through api_status_error, so 401 responses emit the structured auth_required guest error.
coderabbitai “Route Drive multipart upload failures through api_status_error too.” Applied that path and added a host-runtime regression for google-drive.upload_file returning RuntimeCapabilityOutcome::AuthRequired on a 401.

Validation

  • CARGO_TARGET_DIR=/Volumes/NVME/ironclaw-target cargo component build --release --target wasm32-wasip2 --manifest-path crates/ironclaw_first_party_extensions/assets/google-drive/wasm-src/Cargo.toml
  • TMPDIR=/Volumes/NVME/ironclaw-tmp CARGO_TARGET_DIR=/Volumes/NVME/ironclaw-target CARGO_BUILD_JOBS=2 cargo test -p ironclaw_host_runtime --test github_wasm_runtime_contract host_runtime_services_maps_google_drive -- --nocapture
  • CARGO_TARGET_DIR=/Volumes/NVME/ironclaw-target cargo test -p ironclaw_host_runtime --test github_wasm_runtime_contract host_runtime_services_routes_google -- --nocapture
  • cargo fmt --check --package ironclaw_host_runtime
  • rustfmt --check --edition 2021 crates/ironclaw_first_party_extensions/assets/google-drive/wasm-src/src/api.rs
  • git diff --check
  • TMPDIR=/Volumes/NVME/ironclaw-tmp bash scripts/pre-commit-safety.sh
  • TMPDIR=/Volumes/NVME/ironclaw-tmp CARGO_TARGET_DIR=/Volumes/NVME/ironclaw-target CARGO_BUILD_JOBS=2 cargo clippy -p ironclaw_host_runtime --test github_wasm_runtime_contract --all-features -- -D warnings

GitHub checks: pending on the new head.

@serrrfirat
serrrfirat enabled auto-merge June 17, 2026 14:53
@serrrfirat
serrrfirat requested a review from zetyquickly June 17, 2026 15:24
zetyquickly
zetyquickly previously approved these changes Jun 23, 2026

@zetyquickly zetyquickly left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approving. Verified end-to-end on this branch — forced a 401 on a real google-drive.list_files call and confirmed the guest now emits auth_required → RuntimeCapabilityOutcome::AuthRequired → reauth gate, where main produced a retryable operation_failed. Audited all four tools: every non-2xx path routes through api_status_error (the upload_file gap is fixed).

This resolves the core of #4991. For the close note: token refresh is handled upstream by proactive refresh at staging (#5053 / #5087 / #4174), so the issue's "reactive refresh-retry parity" is a superseded non-goal, not missing — every credential branch (refreshable / revoked / no-refresh-token / missing) now lands correctly.

Non-blocking: the 401→auth_required regression covers Drive only; since the helper is copy-pasted per tool, one test against a non-Drive tool (e.g. google-docs.get_document) would lock the contract for all four.

LGTM 🚢

@serrrfirat
serrrfirat added this pull request to the merge queue Jun 23, 2026
Merged via the queue into main with commit a37f782 Jun 23, 2026
43 checks passed
@serrrfirat
serrrfirat deleted the codex/google-wasm-auth-required branch June 23, 2026 20:54
henrypark133 added a commit that referenced this pull request Jun 25, 2026
…on/surface

Addresses the CI failure and the remaining review feedback on the
credential_requirements enrichment PR.

- github_wasm_runtime_contract.rs: the two google-drive WASM 401 tests
  asserted credential_requirements.is_empty() — the pre-fix, un-wired
  contract from #4969 (provider-null, unsubmittable gate, #5174). The
  enrichment now populates the gate from the single credential
  obligation, so assert one requirement with provider=google + OAuth
  setup. This is the runtime-401 re-auth fallback; proactive refresh
  (inline + background keepalive) already runs before injection.

- host.rs: document the reactive-refresh-on-runtime-401 follow-up on the
  enrichment helper, and correct the downstream-consumer note (OAuth
  setup launches the OAuth flow, ManualToken renders the token card).

- credential.rs: add direct unit tests for binding_scope_owns_account
  covering the session_id and surface exact-match branches. The durable
  filesystem caller tests partition account records by surface+session
  path, so those axes only ever returned CredentialMissing and never
  executed the guard's equality branches (coderabbit review point).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
henrypark133 added a commit that referenced this pull request Jun 25, 2026
* fix(reborn): populate provider on runtime auth-required gates

A WASM/runtime capability whose injected credential returns 401 raises an
`auth_required` gate with empty `credential_requirements`
(`runtime_adapters.rs` `wasm_guest_dispatch_error`). That left
`AuthPromptView.provider` null, so the WebUI manual-token card threw
client-side (`useChat.submitAuthToken` requires `provider`) and never sent
the submit — surfacing as "Could not save the token" with no network request.

Enrich an empty `DispatchError::AuthRequired.credential_requirements` from the
capability's already-declared credential obligations
(`InjectCredentialAccountOnce` -> `RuntimeCredentialAuthRequirement`) in the
capability host, where both the dispatch result and the obligations are in
scope. Runtime-agnostic; never overrides a populated list. This reuses the
same declared-requirement data the credential-missing path already surfaces,
so re-auth gates become submittable.

Adds a caller-level regression test driving `CapabilityHost::invoke_json`
that asserts the gate carries the provider (fails before the fix), plus a
non-override test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(host-api): move auth-requirement enrichment onto host_api types; fix take-one

Address code-review findings on the runtime auth-required enrichment:

- Correctness: emit at most one credential requirement. The downstream
  consumer (auth_prompt_from_credential_requirement) matches exactly one
  (`let [requirement] = ...`); emitting >1 for capabilities with multiple
  credential obligations made it fall through and leave the gate
  unsubmittable. Enrich with `.take(1)`.
- Altitude/duplication: move the logic onto the types that own it in
  ironclaw_host_api — `Obligation::credential_auth_requirement()` and
  `DispatchError::enrich_auth_requirements(&[Obligation])`. Delete the
  free helper from the capability host (host.rs shrinks; the two call
  sites become one-liners and can't drift).
- Tests: add resume-path coverage (auth_resume_json -> dispatch_resumed_
  capability), a multi-obligation test locking the take-one contract, and
  host_api unit tests for both new methods.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(reborn): owner-granularity scope check for manual-token/selection completion

Folds the second link of runtime credential re-auth into this PR.

Manual-token (and credential-selection) submit mints a fresh per-request
`invocation_id`, so completing a flow that reconnects to a credential account
created in an earlier flow failed `scope_matches` full-equality with
CrossScopeDenied (HTTP 403) — the "Could not save the token" follow-on once the
gate became submittable. This is #4935 defect A on the unbound/reusable path.

- `complete_manual_token` and `complete_credential_selection`
  (product_auth_durable/flows.rs) now use `binding_scope_owns_account`:
  owner-granularity (tenant/user/agent/project hard-required, session + surface
  exact-matched) while ignoring the ephemeral invocation_id (and thread/mission,
  intentional for owner-reusable accounts).
- Mirror the same fix in the in-memory fake (fakes.rs) so it cannot mask the
  divergence in unit tests.
- Tests: cross-invocation reconnect succeeds (both paths); genuinely foreign
  owner still rejected; cross-session and cross-surface still rejected
  (path-partitioned on disk).

Follow-ups (not in this PR): rename `binding_scope_owns_account` ->
`scope_owns_account` (now used on unbound paths too); extract a shared
completion-account validation helper to unify the three call sites.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor: move auth-requirement enrichment policy to capabilities; tighten condition

Address PR #5180 review (thermo-nuclear + multi-agent):

- Altitude: keep the neutral `Obligation::credential_auth_requirement` mapper
  in ironclaw_host_api, but move the enrichment POLICY out of
  `DispatchError::enrich_auth_requirements` (product-workflow cardinality has no
  place in the neutral vocab crate per its guardrail) into a private helper in
  ironclaw_capabilities.
- Correctness: synthesize the auth-gate credential requirement ONLY when the
  runtime gave no auth signal of its own (both `required_secrets` and
  `credential_requirements` empty) AND the capability declares EXACTLY ONE
  credential obligation. Raw-secret gates (required_secrets populated) are no
  longer mis-prompted as product-auth; multi-credential capabilities no longer
  get a wrong-provider gate (was `.take(1)` guessing the first).
- Tests: unit tests for all helper branches; updated the multi-obligation
  contract test to assert the gate is left unmodified (empty) rather than
  pointed at an arbitrary provider.

Also adds durable rejection coverage for complete_credential_selection
(foreign owner reaches binding_scope_owns_account -> CrossScopeDenied; session/
surface are path-partitioned -> CredentialMissing, guard exact-match is
defense-in-depth).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(reborn): fix runtime-401 reauth-gate contract; cover guard session/surface

Addresses the CI failure and the remaining review feedback on the
credential_requirements enrichment PR.

- github_wasm_runtime_contract.rs: the two google-drive WASM 401 tests
  asserted credential_requirements.is_empty() — the pre-fix, un-wired
  contract from #4969 (provider-null, unsubmittable gate, #5174). The
  enrichment now populates the gate from the single credential
  obligation, so assert one requirement with provider=google + OAuth
  setup. This is the runtime-401 re-auth fallback; proactive refresh
  (inline + background keepalive) already runs before injection.

- host.rs: document the reactive-refresh-on-runtime-401 follow-up on the
  enrichment helper, and correct the downstream-consumer note (OAuth
  setup launches the OAuth flow, ManualToken renders the token card).

- credential.rs: add direct unit tests for binding_scope_owns_account
  covering the session_id and surface exact-match branches. The durable
  filesystem caller tests partition account records by surface+session
  path, so those axes only ever returned CredentialMissing and never
  executed the guard's equality branches (coderabbit review point).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(reborn): fix stale symbol/line refs in auth scope comments

Address two low-severity review nits:
- capability_host_auth_required_enrichment_contract.rs: header referenced
  the old helper name enrich_auth_required_from_obligations; rename to the
  real enrich_dispatch_error_credential_requirements.
- fakes.rs / flows.rs: scope comments hard-coded credential.rs:580, which
  is already stale (binding_scope_owns_account is now at line 607). Drop
  the line number and point at the symbol only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(capabilities): cover raw-secret gate preservation; soften refresh doc

Address two review nits on the enrichment helper:
- host.rs: the OAuth runtime-401 follow-up doc stated cross-layer refresh
  (inline injection + background keepalive) as a guarantee, but those live
  in other crates and are not enforced here. Soften to "may already have
  been attempted".
- capability_host_auth_required_enrichment_contract.rs: add
  invoke_json_preserves_required_secrets_from_dispatcher — a caller-level
  test driving CapabilityHost::invoke_json with a raw-secret AuthRequired
  (required_secrets populated, credential_requirements empty) while an
  InjectCredentialAccountOnce obligation is declared, asserting the gate is
  left unmodified (secrets preserved, not rewritten into a provider prompt).
  Previously covered only at the private-helper level.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor: core 20+ merged PRs risk: low Changes to docs, tests, or low-risk modules size: M 50-199 changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants