Skip to content

fix(reborn): keep OAuth auth gates visible without auth URL - #5067

Merged
think-in-universe merged 6 commits into
mainfrom
codex/oauth-auth-gate-unavailable-url
Jun 18, 2026
Merged

think-in-universe merged 6 commits into
mainfrom
codex/oauth-auth-gate-unavailable-url

Conversation

@hanakannzashi

@hanakannzashi hanakannzashi commented Jun 18, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Preserve OAuth challenge semantics when an OAuth credential requirement has no authorization URL.
  • Keep the chat OAuth auth card visible instead of falling back to the generic auth prompt.
  • Surface Service Unavailable from the OAuth card only after the authorize action is clicked.
  • Remember locally resolved auth gates so stale projection replay does not restore the cancelled OAuth/fallback card or leave the run displayed as stuck.
  • Resume auth cancel/deny as a denied auth gate result so the model can continue instead of terminating or leaving the activity running.
  • Keep approval -> auth checkpoints unambiguous by folding prior approval identity into pending_auth_resume and clearing pending_approval_resume at the auth gate.
  • Project live capability completion/failure states so tool activities do not stay RUN until refresh.
  • Add regression coverage for OAuth auth prompts without authorization URLs, auth gate cancel resolution, approval -> auth resume state, and live failed tool activity projection.

Closes #5066
Closes #5070

Tests

  • npm run build
  • node --test crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/gates.test.mjs
  • node --test crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/useChat-send.test.mjs
  • node --test crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/useChatEvents.test.mjs
  • node --test crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/*.test.mjs
  • cargo test -p ironclaw_webui_v2_static --features webui-v2-beta --test i18n_consistency
  • cargo test -p ironclaw_reborn_composition webui_event_stream_keeps_oauth_requirement_as_oauth_prompt_without_url --features webui-v2-beta
  • cargo fmt --check
  • cargo test -p ironclaw_agent_loop auth_resume_after_approval_carries_resume_token_and_approval_request_id
  • cargo test -p ironclaw_product_workflow denied_auth_without_flow_record
  • cargo test -p ironclaw_reborn stamp_resume_disposition_fails_closed_when_both_slots_match_last_gate
  • cargo test -p ironclaw_reborn_composition webui_event_stream_projects_live_tool_failure --features webui-v2-beta

@railway-app

railway-app Bot commented Jun 18, 2026 •

Copy link
Copy Markdown

🚅 Deployed to the ironclaw-pr-5067 environment in ironclaw-ci-preview

Service Status Web Updated (UTC)
ironclaw ✅ Success (View Logs) Web Jun 18, 2026 at 10:45 am

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5067 June 18, 2026 07:44 Destroyed
@github-actions github-actions Bot added the size: M 50-199 changed lines label Jun 18, 2026
@coderabbitai

coderabbitai Bot commented Jun 18, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Updates OAuth challenge kind from Other to OAuthUrl and surfaces user-facing "Service Unavailable" error in AuthOauthCard when authorization URL is absent. Fixes auth gate cancellation after approval by clearing pending_approval_resume at auth gates, centralizing auth interaction service resume logic through a new helper, enriching capability activity terminal state through projections, and adding local gate resolution tracking in WebUI.

Changes

OAuth unavailable URL handling

Layer / File(s) Summary
OAuthUrl contract and challenge_kind assignment
crates/ironclaw_product_adapters/src/outbound.rs, crates/ironclaw_reborn_composition/src/auth_prompt.rs
OAuthUrl doc comment clarifies absent authorization_url case; auth_prompt_from_credential_requirement assigns OAuthUrl instead of Other for OAuth credential requirements.
AuthOauthCard error state for missing HTTPS URL
crates/ironclaw_webui_v2_static/static/js/pages/chat/components/auth-oauth-card.js
Adds error state, effect clearing on gate identity change, openAuth handler guarding non-HTTPS by setting "Service Unavailable", and conditional red alert rendering.
i18n strings for service unavailable message
crates/ironclaw_webui_v2_static/static/js/i18n/*
Adds authGate.serviceUnavailable translations across Arabic, German, English, Spanish, French, Hindi, Japanese, Korean, Portuguese (Brazil), Ukrainian, and Chinese language packs.
Rust and JS test updates for OAuthUrl
crates/ironclaw_reborn_composition/src/projection/tests/turn_stream_auth.rs, crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/gates.test.mjs
turn_stream_auth test renamed; AuthPrompt matcher asserts challenge_kind == OAuthUrl; gates.test.mjs adds invocationId: null and new oauth_url challenge_kind preservation test.

Auth gate cancellation after approval fix

Layer / File(s) Summary
Executor: clear pending_approval_resume at auth gates
crates/ironclaw_agent_loop/src/executor/gates.rs, crates/ironclaw_agent_loop/src/executor/tests.rs
GateStage::process under GateOutcome::Block conditionally initializes pending_approval_resume only for GateKind::Approval; auth gates explicitly clear it to None. Test asserts phase-2 BeforeBlock state clears the field.
Auth interaction service: centralize resume logic
crates/ironclaw_product_workflow/src/auth_interaction/service.rs
New resume_auth_gate helper centralizes GetRunStateRequest and ResumeTurnRequest construction. Refactors deny paths and cancel_parked_auth_without_flow to delegate through helper with optional GateResumeDisposition. Routes MissingAuth+Deny through resume_denied_auth_without_flow.
Auth interaction contract test: resume-based denial
crates/ironclaw_product_workflow/tests/auth_interaction_contract.rs
Test verifies denying auth without auth flow record triggers resume: zero flow cancellations, one coordinator resume_turn with BlockedAuthGate precondition and Denied disposition.
Event stream: capability activity serde and optional fields
crates/ironclaw_event_streams/src/types.rs
ThreadLiveProjectionItem::CapabilityActivity gains serde default for status and optional fields provider/runtime/output_bytes/error_kind with conditional serialization. Adds default_capability_activity_status helper.
Live progress: publish terminal capability activity data
crates/ironclaw_reborn_composition/src/projection/live_progress.rs
product_items_for_live_update uses dynamic status and terminal fields via live_capability_activity_status helper. publish_capability_activity accepts explicit status and TerminalCapabilityActivity payload. publish_loop_milestone maps milestone kinds to Started/Completed/Failed with terminal data.
Live progress stream test: tool failure milestone
crates/ironclaw_reborn_composition/src/projection/tests/live_progress_stream.rs
Adds test verifying CapabilityFailed milestone projects to Failed capability activity with authorization error_kind.
WebUI: local gate resolution tracking and component tests
crates/ironclaw_webui_v2_static/static/js/pages/chat/hooks/useChat.js, crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/chat-input.test.mjs, crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/useChat-send.test.mjs
useChat maintains locallyResolvedGatesRef Map keyed by runId+gateRef, cleared per-thread. resolveGate stores resolution outcome. Test harness extended with auth/staging/attachment mocks. Gate-resolution tests assert local storage contents and cleared pending states.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • nearai/ironclaw#4957: Overlaps on OAuth challenge kind mapping in auth_prompt_from_credential_requirement and WebUI gate rendering.

Suggested reviewers

  • serrrfirat
  • think-in-universe

Poem

🔐 OAuth stays visible when URLs turn to dust,
pending_approval_resume no longer bleeds through —
auth gates fold state cleanly, as they must,
while terminal capability data flows true.
The card shows "Unavailable," the cancel works right,
resuming with denial, restoring the light. ✨

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description check ✅ Passed PR description provides substantial context but does not follow the required template structure (Summary section format, explicit Change Type checkboxes, Validation checklist, etc.).
Linked Issues check ✅ Passed All linked objectives from #5066 and #5070 are met: OAuth cards visible without auth URLs, Service Unavailable errors on user action, local gate resolution tracking, auth-deny resumption, approval-to-auth state clarity, and live capability state projection.
Out of Scope Changes check ✅ Passed All changes are in scope: OAuth auth prompt semantics, gate resolution tracking, approval-to-auth state management, capability activity projection, i18n for service unavailable, and corresponding test coverage.
Title check ✅ Passed Title follows Conventional Commits style (type(scope): summary) and accurately describes the main change: preserving OAuth authentication gates visibility without authorization URL.

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


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

@github-actions github-actions Bot added risk: low Changes to docs, tests, or low-risk modules contributor: core 20+ merged PRs labels Jun 18, 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 updates the OAuth challenge handling to preserve the OAuthUrl challenge kind even when the authorization URL is absent (e.g., when the provider is unconfigured or unavailable), allowing the UI to render an OAuth-specific unavailable state. In the WebUI, the OAuth card button is no longer disabled when the URL is missing; instead, clicking it displays a "Service Unavailable" error message. The review feedback suggests using the translation helper t() for this error message to support internationalization.

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.

@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_webui_v2_static/static/js/pages/chat/components/auth-oauth-card.js`:
- Around line 52-55: The hardcoded error message "Service Unavailable" in the
setError() call within the hasHttpsAuthorizationUrl validation block is not
internationalized, while the rest of the component consistently uses the t()
i18n hook for all user-facing text. Replace the hardcoded string with t() using
an appropriate i18n key (e.g., "auth.service_unavailable" or similar), then add
this new key with translations to all i18n resource files (en.js, es.js, fr.js,
etc.) to maintain consistency with the component's existing i18n pattern.
🪄 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: 823daf37-bb06-4fb0-8904-5eca79a9463f

📥 Commits

Reviewing files that changed from the base of the PR and between 0e3c22b and 4ba98a1.

⛔ Files ignored due to path filters (1)
  • crates/ironclaw_webui_v2_static/static/dist/app.js is excluded by !**/dist/**
📒 Files selected for processing (5)
  • crates/ironclaw_product_adapters/src/outbound.rs
  • crates/ironclaw_reborn_composition/src/auth_prompt.rs
  • crates/ironclaw_reborn_composition/src/projection/tests/turn_stream_auth.rs
  • crates/ironclaw_webui_v2_static/static/js/pages/chat/components/auth-oauth-card.js
  • crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/gates.test.mjs

@think-in-universe

Copy link
Copy Markdown
Collaborator

@claude review

@think-in-universe

Copy link
Copy Markdown
Collaborator

✅ Codex review for current head 4ba98a11d6de816b9697d5033be3ebb8f6f9bc4e.

I reviewed the OAuth auth-gate projection/UI change and did not find a new blocking correctness or security issue beyond the existing automated i18n comments on the hardcoded "Service Unavailable" message in auth-oauth-card.js. Please address that by routing the message through the WebUI i18n resources.

Local verification from an isolated worktree at the PR head:

  • node --test crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/gates.test.mjs
  • cargo test -p ironclaw_reborn_composition webui_event_stream_keeps_oauth_requirement_as_oauth_prompt_without_url

Not ready for final human-review guidance yet: Claude review is now requested, CI still has pending Reborn/clippy/gateway jobs, and the i18n review thread remains unresolved.

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5067 June 18, 2026 07:53 Destroyed
@github-actions github-actions Bot added size: XL 500+ changed lines and removed size: M 50-199 changed lines labels Jun 18, 2026
@think-in-universe

Copy link
Copy Markdown
Collaborator

✅ Codex follow-up for current head 4b708cc305c6436cbfc933acabc5ad8c70e15039.

I reviewed the new Suppress stale auth gate replay after cancel commit. No new blocking findings from the fresh diff. The change wires the local gate-resolution record into the existing projection replay suppression path and adds coverage for resumed auth-cancel terminal handling.

Local verification from isolated PR worktree:

  • node --test crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/useChat-send.test.mjs crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/useChatEvents.test.mjs crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/chat-input.test.mjs

Still not ready for final human-review guidance: the existing i18n threads on auth-oauth-card.js are still unresolved, and Claude review was requested before this latest commit and has not reported back yet.

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5067 June 18, 2026 09:41 Destroyed
@think-in-universe

Copy link
Copy Markdown
Collaborator

@claude review

@think-in-universe

Copy link
Copy Markdown
Collaborator

✅ Codex follow-up for current head 6b2278170a8e72e431891c1cfc5bae81ee8904d2.

Reviewed the new Fix auth gate cancel activity state commit. I did not find a new blocking issue in the fresh diff: auth-deny now resumes the blocked auth gate with a denied disposition instead of cancelling parked runs without a flow record, the auth gate path clears the stale pending approval slot when folding prior approval into pending_auth_resume, and live progress now projects terminal capability activity state so the WebUI can reflect failed tool activity after denial.

Focused local verification from an isolated PR worktree:

  • cargo test -p ironclaw_agent_loop auth_resume_after_approval_carries_resume_token_and_approval_request_id --lib
  • cargo test -p ironclaw_product_workflow denied_auth_without_flow_record_resumes_parked_auth_run
  • cargo test -p ironclaw_reborn_composition webui_event_stream_projects_live_tool_failure --lib
  • node --test crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/useChat-send.test.mjs crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/chat-input.test.mjs

Still not ready for final human-review guidance: CI is pending for this new head, Claude has been re-requested for the current runtime changes, and the existing i18n review threads on auth-oauth-card.js remain unresolved.

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5067 June 18, 2026 10:00 Destroyed

@think-in-universe think-in-universe left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

❌ Codex follow-up for current head 096a9ac4332bc2c4c15f4a0999d04730bc1a4dd6.

I found one blocking packaging issue in the latest i18n fix: the source module now uses authGate.serviceUnavailable, but the committed WebUI v2 bundle still contains the old hardcoded "Service Unavailable" path. Since static/index.html loads /v2/dist/app.js, this fix will not reach the shipped UI until the bundle is regenerated and committed.

Focused checks run locally:

  • cargo test -p ironclaw_webui_v2_static --test i18n_consistency — passed
  • node --test crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/gates.test.mjs crates/ironclaw_webui_v2_static/static/js/lib/i18n.test.mjs — passed
  • rg over source + static/dist/app.js — found the stale bundle string

I did not re-request Claude for this small follow-up commit; the remaining issue is concrete and localized.

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5067 June 18, 2026 10:07 Destroyed
@hanakannzashi

Copy link
Copy Markdown
Contributor Author

Fixed in 937b6e6: rebuilt and committed the WebUI v2 static/dist bundle so the shipped app now uses authGate.serviceUnavailable instead of the stale hardcoded string.\n\nVerified with:\n- bash crates/ironclaw_webui_v2_static/frontend/build.sh --no-vendor\n- rg over static/dist for the old "Service Unavailable" string\n- node --test crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/gates.test.mjs crates/ironclaw_webui_v2_static/static/js/lib/i18n.test.mjs\n- cargo test -p ironclaw_webui_v2_static --features webui-v2-beta --test i18n_consistency

@think-in-universe think-in-universe left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

✅ Codex follow-up for current head 937b6e6b09f73bd893c6ed92295481adce87130a.

The stale WebUI v2 bundle blocker from my previous review is resolved: static/dist/app.js now calls authGate.serviceUnavailable, the old hardcoded "Service Unavailable" path is gone, and the locale chunk imports point at the rebuilt chunk names. I did not find a new blocking issue in this rebuild-only follow-up.

Focused checks run locally:

  • cargo test -p ironclaw_webui_v2_static --test i18n_consistency — passed
  • cargo test -p ironclaw_webui_v2_static --features webui-v2-beta --test i18n_consistency — passed
  • node --test crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/gates.test.mjs crates/ironclaw_webui_v2_static/static/js/lib/i18n.test.mjs — passed
  • rg over static/dist — confirmed the stale hardcoded string is gone and the rebuilt chunks are referenced

This is not an approval or final human-review guidance yet; CI is still pending on the new head.

@think-in-universe
think-in-universe dismissed their stale review June 18, 2026 10:09

Superseded by Codex follow-up on head 937b6e6; the stale WebUI v2 bundle issue was fixed and the review thread is resolved.

@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_reborn_composition/src/projection/tests/live_progress_stream.rs`:
- Around line 232-241: The test is brittle because it only inspects
state.items.first() when filtering for
ProductProjectionItem::CapabilityActivity, which means if item ordering changes,
the CapabilityActivity could be missed. Instead of using filter_map with
state.items.first(), iterate through all items in the state.items collection to
find the CapabilityActivity variant. Additionally, add an assertion on the
runtime field of the CapabilityActivity to ensure the newly projected terminal
field is properly covered by the test, fulfilling the requirement to test
through the actual caller and its side effects.
🪄 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: 10137701-a506-41c5-8b37-158e9c11367a

📥 Commits

Reviewing files that changed from the base of the PR and between 4ba98a1 and 096a9ac.

⛔ Files ignored due to path filters (1)
  • crates/ironclaw_webui_v2_static/static/dist/app.js is excluded by !**/dist/**
📒 Files selected for processing (22)
  • crates/ironclaw_agent_loop/src/executor/gates.rs
  • crates/ironclaw_agent_loop/src/executor/tests.rs
  • crates/ironclaw_event_streams/src/types.rs
  • crates/ironclaw_product_workflow/src/auth_interaction/service.rs
  • crates/ironclaw_product_workflow/tests/auth_interaction_contract.rs
  • crates/ironclaw_reborn_composition/src/projection/live_progress.rs
  • crates/ironclaw_reborn_composition/src/projection/tests/live_progress_stream.rs
  • crates/ironclaw_webui_v2_static/static/js/i18n/ar.js
  • crates/ironclaw_webui_v2_static/static/js/i18n/de.js
  • crates/ironclaw_webui_v2_static/static/js/i18n/en.js
  • crates/ironclaw_webui_v2_static/static/js/i18n/es.js
  • crates/ironclaw_webui_v2_static/static/js/i18n/fr.js
  • crates/ironclaw_webui_v2_static/static/js/i18n/hi.js
  • crates/ironclaw_webui_v2_static/static/js/i18n/ja.js
  • crates/ironclaw_webui_v2_static/static/js/i18n/ko.js
  • crates/ironclaw_webui_v2_static/static/js/i18n/pt-BR.js
  • crates/ironclaw_webui_v2_static/static/js/i18n/uk.js
  • crates/ironclaw_webui_v2_static/static/js/i18n/zh-CN.js
  • crates/ironclaw_webui_v2_static/static/js/pages/chat/components/auth-oauth-card.js
  • crates/ironclaw_webui_v2_static/static/js/pages/chat/hooks/useChat.js
  • crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/chat-input.test.mjs
  • crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/useChat-send.test.mjs

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5067 June 18, 2026 10:40 Destroyed
@hanakannzashi

Copy link
Copy Markdown
Contributor Author

Fixed the live progress stream test nit in de663ef: the test now scans all projection items for the capability activity instead of only state.items.first(), and it asserts the projected runtime field.\n\nVerified with:\n- cargo test -p ironclaw_reborn_composition webui_event_stream_projects_live_tool_failure --lib\n- cargo fmt --check

@think-in-universe

Copy link
Copy Markdown
Collaborator

✅ Codex follow-up for current head de663eff6e84ef6567c63cb542b9ee97910b6268.

Reviewed the new Harden live tool failure projection test commit. No new blocking findings from the fresh diff: the test now scans all projection items for the failed capability activity and asserts the projected RuntimeKind::FirstParty, which addresses the prior test-hardening feedback.

Focused verification from an isolated PR worktree at this head:

  • cargo test -p ironclaw_reborn_composition webui_event_stream_projects_live_tool_failure --lib — passed

I did not re-request Claude for this test-only follow-up. This is still not final human-review guidance because CI and automated review are pending for the new head.

@think-in-universe

Copy link
Copy Markdown
Collaborator

✅ Codex final-review guidance

No blocking findings from my latest review of de663eff6e84ef6567c63cb542b9ee97910b6268. The focused local check I ran earlier passed, and CI is now clear from my current poll.

Human reviewers should still focus on:

  • OAuth auth-gate behavior when no auth URL is available, including user-facing unavailable states.
  • Cancellation/resume paths after auth approval or denial, especially pending approval/auth transitions.
  • Product workflow auth_interaction denial/resume behavior.
  • Live capability terminal projection metadata and WebUI gate suppression.
  • The static WebUI/i18n surface for this state.

I’m not approving or merging; this is ready for human review.

@think-in-universe think-in-universe changed the title Keep OAuth auth gates visible without auth URL fix(reborn): keep OAuth auth gates visible without auth URL Jun 18, 2026
@think-in-universe
think-in-universe added this pull request to the merge queue Jun 18, 2026
Merged via the queue into main with commit 553e439 Jun 18, 2026
71 checks passed
@think-in-universe
think-in-universe deleted the codex/oauth-auth-gate-unavailable-url branch June 18, 2026 12:43
abbyshekit pushed a commit to nearai/ironclaw-desktop-app that referenced this pull request Jun 18, 2026
…#5067)

The OAuth auth gate (AuthOauthCard, challengeKind "oauth_url") previously
disabled the Authorize button and silently no-op'd when the gateway had no
HTTPS authorization URL yet — a dead gate with no explanation.

Port of upstream #5067, adapted to the desktop's diverged component:
- keep the button actionable; on click without a valid HTTPS URL, show an
  inline "Service unavailable" alert instead of doing nothing
- clear the error when the gate changes (authorizationUrl/gateRef/runId)
- security guard intact (issue #4112): openAuth still rejects missing/
  non-HTTPS URLs before window.open, href stays conditional

i18n: add authGate.serviceUnavailable to all 11 locales with upstream's
translations; bump i18n-completeness expected en key count 1079 -> 1080.

Surgical/additive only — no other upstream divergence pulled. Upstream's
net-new modules (workspace-breadcrumb/directory, automations-empty-state)
were evaluated and skipped: the desktop already covers them via its own
diverged workspace UI and EmptyPanel-based automations empty state.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
theredspoon pushed a commit to theredspoon/ironclaw that referenced this pull request Jun 21, 2026
)

* Keep unavailable OAuth gates on OAuth card

* Suppress stale auth gate replay after cancel

* Fix auth gate cancel activity state

* Fix OAuth auth gate unavailable i18n

* Rebuild WebUI v2 bundle

* Harden live tool failure projection test
henrypark133 added a commit that referenced this pull request Jul 4, 2026
… pack, triggered auth delivery, attachments, golden/synthetic expansions (#5610)

* test(reborn): doc/text + multi-attachment coverage (W4-ATTACH-VARIANTS)

Adds submit_turn_with_attachments (generalizes the image-only
submit_turn_with_image_attachment to N attachments of any mime type)
and two int-tier tests: a text/plain attachment's extracted text
reaching the model, and two attachments in one turn both reaching the
model with distinct index ordinals. Closes the doc/multi-attachment
gap in C-ATTACH (only single-image coverage existed before).

* test(reborn): W4-AUTHGATE-WIRE — runtime-401 provider-gate + cancel-no-replay (wave-4 row 1)

Pins the #5174/#5180 bug class (empty credential_requirements leaving
AuthPromptView.provider null, "Could not save the token" with no network
request) through the FULL scripted-gateway integration harness — a tier
below the existing crate-level pins, which drive CapabilityHost::invoke_json
or HostRuntimeServices::invoke_capability directly and never exercise the
real submit_turn -> BlockedAuth wire the WebUI depends on.

- tests/reborn_integration_auth_gate.rs: new
  runtime_401_after_injection_populates_provider_credential_requirement
  (github credential resolves OK but the runtime HTTP call 401s; asserts
  the resulting BlockedAuth gate's credential_requirements carries
  provider=github + ManualToken setup), cancel_blocked_auth_gate_leaves_no_stale_replay
  (cancelling a BlockedAuth run lands directly on Cancelled with no active
  worker, and the SAME real gate ref can no longer resume it afterward —
  closes the #5067/#4957 class of gates staying "live"), and
  deny_auth_gate_rejects_a_non_auth_gate_ref_prefix (negative companion).
  Flip-check: temporarily bypassed the host.rs enrichment call site,
  confirmed the flagship test fails with the exact pre-fix empty-list
  shape, restored (crates/ironclaw_capabilities/src/host.rs left
  byte-identical — no production diff).

- tests/support/reborn/harness.rs: RecordingNetworkHttpEgress gains an
  additive FIFO status_queue (default empty -> unchanged hardcoded-200
  behavior) + install_network_status_script accessor. Needed because
  GithubIssueTools' real WASM HTTP call flows through the network-egress
  lane, not the runtime-egress lane the existing ScriptedHttpResponse
  matcher scripts (try_with_host_http_egress overwrites the runtime port —
  see reborn_integration_secret_injection.rs's module doc) — the prior
  double had no way to script a non-200 status on that lane at all.
- tests/support/reborn/builder.rs: with_github_network_status(status)
  builder method (FIFO) threading github_network_statuses through
  RebornCapabilityBackend::install.
- tests/support/reborn/capability_backend.rs: wires keyed_http_responses
  (previously dropped for this backend) and the new github_network_statuses
  into the GithubIssueTools install arm; no-op for existing empty-vec callers.
- tests/support/reborn/assertions.rs: assert_network_egress_count, sibling
  of assert_egress_count for the network-lane call-count proofs above.

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

* test(reborn): unknown extension_id fails extension_install safely (W4-EXT-MANIFEST-ERR)

Narrowed from the originally-scoped manifest-content arms (schema
mismatch/reserved id/forbidden trust level): extension_install's only
input is a catalog-resolved extension_id over a fixed,
compile-time-embedded bundled catalog, so raw manifest TOML never
reaches ManifestV2Error validation through this capability in
production. The one reachable, wired arm is an unknown extension_id,
which fails Failed{invalid_input} rather than panicking or no-oping.

* test(reborn): W4-PROVIDER-VALIDATE — password/traceback caller-gap coverage

#5001 (PinchBench bucket D) removed the crude SENSITIVE_PROVIDER_TEXT_MARKERS
substring scan on provider reasoning/response_reasoning/signature text (bare
words like "password"/"traceback" were false-positive-rejected, driving
retry/give-up loops); the entropy-based LeakDetector is the real guard now.
That contract was pinned only at the private free-function level
(capability_port/provider_validation.rs's own unit test calling
validate_provider_tool_call directly) — the #5001 caller gap.

Adds provider_tool_call_registration_accepts_password_and_traceback_reasoning_text
in crates/ironclaw_loop_support/src/capability_port.rs's existing test module,
alongside the crate's other caller-level `port.validate_provider_tool_call(&call)`
tests: drives the REAL production caller
(LoopCapabilityPort::validate_provider_tool_call / register_provider_tool_call
/ invoke_capability on HostRuntimeLoopCapabilityPort, the same port the agent
loop calls) with "password"/"traceback" in all three metadata fields, and
proves genuine acceptance through to a real Completed dispatch (not just a
non-error return).

Flip-check: temporarily bloated response_reasoning past
PROVIDER_METADATA_TEXT_MAX_BYTES to confirm the assertion mechanism
discriminates a genuine rejection (fails with the expected
"exceeds 16384 bytes" error), then restored the password/traceback content.

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

* test(reborn): W4-MCP-SSO-WIRING — NEAR AI host-managed fallback through build_reborn_services

#5439 fixed NEAR AI MCP token resolution for SSO users: a Google-SSO user in
the same tenant/agent as the boot owner, with no NEAR AI token of their own,
now falls back to the host-managed (boot-owner) NEAR AI credential instead of
being prompted for one. That contract was pinned only at the private
rule/selector level (product_auth_runtime_credentials/tests.rs never calls
build_reborn_services) — the composition-wiring gap this row targets.

Adds local_dev_nearai_runtime_selection_falls_back_to_host_managed_account_for_sso_user
to extension_lifecycle_capabilities_auth_tests.rs (extending the existing
in-crate #[cfg(test)] composition-test file — same pattern as the sibling
github manual-token test above, template: product_auth_refresh_composition.rs's
"drive build_reborn_services directly" style). Drives ONLY the public surface:
build_reborn_services (local-dev always derives nearai_mcp_host_managed_scope
from the boot owner, so no live NEAR AI config injection is needed) plus the
crate-internal runtime_credential_account_selection_service() accessor this
file already had precedent for calling. Two discriminating arms on one
composed `services`: an SSO user in the owner's tenant/agent (different
project -- local-dev's host scope is project-unscoped by design) resolves via
fallback; an SSO user under a different tenant does not (CredentialMissing) --
proving the positive arm is a real scope match, not the selector always
succeeding.

Flip-check: temporarily short-circuited
RebornProductAuthServices::runtime_credential_account_selection_service to
always return the un-decorated selector (pre-#5439 behavior), confirmed the
new test's positive arm fails with CredentialMissing, restored (auth.rs left
byte-identical -- no production diff).

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

* test(reborn): C-SYNTH deferred arms — AmbiguousSkill seeding + project_create fault-injection (wave-4 lane C)

Two carry-over arms deferred from wave-3 PR #5584:

- skill_activate AmbiguousSkill: seed a system-scoped AND a user-scoped
  skill sharing one name (both SkillTrust::Trusted per
  FilesystemSkillBundleRoot::system/user) so the real
  validate_explicit_mentions_are_unambiguous reject path fires end-to-end,
  not just at the skill_activation.rs unit-test level. New
  seed_user_skill_for_test harness helper (additive, mirrors
  seed_system_skill_for_test).

- project_create fault-injection: new FaultInjectingProjectService test
  double (project_service_fault.rs) wrapping the real ProjectService at the
  production-wired Arc<dyn ProjectService> seam, forcing
  ProjectServiceError::Denied for a sentinel project name and delegating
  everything else to the real store. New
  project_tools_with_fault_injection()/project_lifecycle_fault_injected()
  harness+group constructors (additive).

  Deliberately NOT ProjectServiceError::Unavailable/Internal: investigation
  found both route through DefaultRecoveryStrategy's capability-retry
  branch, whose retry re-dispatch hits a real, confirmed production bug for
  provider-tool-call-originated invocations under local-dev composition —
  LocalDevCapabilityIo::resolve_capability_input rejects the reused
  input_ref on the retry with InvalidInvocation/"capability input ref was
  not staged for this loop run", collapsing the documented "retry twice,
  then a model-visible Failed" contract into an immediate terminal
  driver_unavailable. Documented in project_service_fault.rs; reported
  separately (not fixed — production change, out of this lane's scope).

Both flip-checked (mutated seed/fault-injection to prove discriminating
failure) and reverted before commit.

* test(reborn): golden payload expansions — parallel tool_calls, image attachment, gated-turn resume (wave-4 lane C)

Three scenario expansions to tests/reborn_integration_golden_payload.rs
(carry-over from wave-3 PR #5584):

- golden_parallel_tool_calls: new RebornScriptedReply::tool_calls([..])
  constructor (additive to reply.rs) scripts ONE assistant response with
  TWO tool_calls[] entries, pinning that multiple calls in one turn each
  get a distinct id and each following tool-role message's tool_call_id
  lines up in order — a shape the existing single-call golden_tool_call_feedback
  can't exercise.

- golden_image_attachment_turn: an inline image landed through the real
  submit_inbound_with_attachments entry point
  (RebornIntegrationGroup::attachment_tools()), routed through a
  vision-pattern model id, pinning the multimodal ContentPart::ImageUrl
  data: URL alongside the text part byte-for-byte.

- golden_gated_turn_approve: a real BlockedApproval gate raised, approved,
  and resumed (RebornIntegrationGroup::live_approvals()), snapshotting BOTH
  inference calls around the gate — proving the resume doesn't drop,
  duplicate, or reorder accumulated turn history.

Two normalization fixes to golden.rs, both needed for these scenarios to be
reproducible (discovered while authoring, not pre-existing regressions):

- Attachment-landing scenarios embed today's real UTC date in the landed
  project path (chrono::Utc::now(), no test seam) — added a second
  <DATE> filter alongside the existing loop-start-clock <TIMESTAMP> filter,
  or the image golden would bit-rot on every day boundary.

- Tool-call ids come from a NEXT_TOOL_CALL_ID counter shared by every test
  in this one compiled binary; running more than one tool-call-scripting
  golden test concurrently (the default `cargo test` thread pool) makes the
  raw id values order-dependent. Added normalize_tool_call_ids: renumbers
  every call-<N> to a canonical call-1, call-2, … in order of first
  appearance per rendered payload, preserving the id/tool_call_id linkage
  the golden actually cares about without depending on the racy raw value.
  Confirmed behavior-preserving for the four pre-existing snapshots (no
  diff) and confirmed the race is fixed (5 consecutive full-suite green
  runs). Flip-checked (forced two parallel tool_calls to share one id;
  golden correctly failed) and reverted before commit.

* test(reborn): W4-ASK-EACH-ONCE — ask-each-time approval resumes exactly once

#5306 fixed an unresumable BlockedApproval loop: require_approval_for_profile_policy
checked the explicit ask_each_time override (and the hard-floor force-approval
class) BEFORE consulting the matching one-shot approval lease a resume
carries, so an approved AskEachTime-gated resume re-hit the ask_each_time
branch and re-gated instead of completing. Only a Python E2E test
(test_tool_approval.py) exercised this class before; no Rust harness
coverage existed.

Adds scenario_ask_each_time_resumes_once.rs to the reborn_group_approvals
binary (both approvals_group_e2e and its libsql variant), run LAST because it
installs a persistent, group-wide ToolPermissionOverride::AskEachTime
override on builtin.write_file that would force-gate every sibling
scenario's plain-Ask-mode writes. Submits under the override, approves the
resulting BlockedApproval gate, and proves the resume reaches Completed in
ONE round trip with the write actually persisted — plus a companion
"resumes exactly once" proof that re-approving the same now-resolved gate_ref
fails NotPending (not a fresh re-raised gate).

tests/support/reborn/harness.rs: adds a generic
tool_permission_overrides: Option<Arc<dyn ToolPermissionOverrideStore>> field
(mirrors the existing auto_approve_settings field's pattern — populated only
by new_with_options, None elsewhere) and
set_ask_each_time_override_for_test, generalizing
disable_outbound_target_set_tool's override-store access beyond
outbound_target_tools() to any host-runtime-backed harness/group.

Flip-check: temporarily restored the pre-#5306 check order in
profile_approval_authorization.rs (ask_each_time/hard-floor before the
one-shot lease), confirmed the new scenario fails (the approved write never
persists), restored (file left byte-identical — no production diff).

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

* test(reborn): triggered-origin chained gated journey (wave-4 lane C)

Carry-over from wave-3 PR #5584: a triggered fire whose run raises a
BlockedApproval gate, gets resolved, then CHAINS into a SECOND
BlockedApproval gate in the SAME run (the post-resume model call issues
another gated tool call instead of finalizing), driven through
submit_triggered_turn_scripted (E-TRIGGERED-SUBMIT).

New scenario_triggered_chained_gate::run_chained_approve, registered as its
own live_approvals group in reborn_group_triggers::triggered_gate_group.
Re-reads TurnOriginKind::ScheduledTrigger fresh at the coordinator boundary
at THREE checkpoints (first park, second/chained park, final Completed) —
not just trusting the initial TriggeredSubmission — closing the gap that a
resume path rebuilding product_context from a non-trigger-aware default on
the SECOND hop would otherwise slip through undetected. Also asserts both
gate_refs are genuinely distinct, both chained writes persisted, and the
final reply persisted in the trigger's own thread.

Flip-checked (asserted the wrong origin kind; the checkpoint helper
correctly failed with the real ScheduledTrigger value in the diagnostic)
and reverted before commit.

Also folds in `cargo fmt` whitespace-only fixes surfaced while formatting
this new file (golden.rs, harness.rs, reply.rs, and two golden/skill-activate
test files touched by prior lane-C commits) — no semantic change, reran
their test bins green after formatting.

* test(reborn): extract trigger-prompt materializer test-support helper (wave-4 lane C)

Committed follow-up on PR #5584's review thread: submit_triggered_turn_scripted
hand-mirrored ConversationContentRefMaterializer::materialize_prompt
(trigger_resolve_request + record_trigger_prompt + the content-ref shape,
field-by-field) instead of reusing it, and — as flagged — deliberately
SKIPPED authorize_trigger_fire and validate_trusted_trigger_prompt. Flagged
as a drift trap (trusted-trigger materialization is an ownership boundary,
AGENTS.md:61); the review agreed the fix is a #[cfg(feature = "test-support")]
materializer helper returning (TriggerMaterializedPrompt, TurnScope) living
beside the real materializer, held out of #5584 as a fast-follow with this
exact shape.

New production-crate (test-support-gated, compiles out of default builds)
surface in ironclaw_reborn_composition:
- trigger_poller_trusted_submit.rs: materialize_trigger_prompt_for_test,
  #[cfg(any(test, feature = "test-support"))] — runs the REAL production
  pipeline via ConversationContentRefMaterializer::materialize_prompt
  (authorize + validate + resolve + record + content-ref), then an
  idempotent second resolve_or_create_binding_with_trusted_scope call (safe
  — same request, same already-created binding) to also return the
  TurnScope the trait method computes internally but never exposes. Plus
  two crate-tier unit tests: positive (returned scope/content-ref match an
  independent ground-truth resolve) and negative (an unsafe prompt is
  rejected by the REAL safety validator).
- test_support/trigger_materializer.rs: pub, feature="test-support"-gated
  thin wrapper re-exported from test_support/mod.rs — the established
  wrap_project_create_capability_for_test-style pattern.

tests/support/reborn/triggered_submit.rs: submit_triggered_turn_scripted now
calls this ONE production-owned helper instead of hand-mirroring; deletes
~90 net lines of duplicated resolve/thread-record/content-ref logic.

Verified default-features build of ironclaw_reborn_composition stays
warning-free (function/import correctly compile out). Flip-checked at the
INTEGRATION level (not just the new crate-unit tests): forced an
injection-pattern prompt through submit_triggered_turn_scripted — every
triggered-gate scenario correctly failed with "rejected by safety scan",
proving the old hand-mirrored path's skip of validate_trusted_trigger_prompt
is now closed. Reverted before commit. All touched integration test bins
(reborn_group_triggers, reborn_integration_triggered_submit, plus every
other wave-4 lane-C bin) rerun green after the extraction.

* test(reborn): W4-TRIGSLACK-SETTLE — auth-gate coverage for TriggeredRunDeliveryDriver

TriggeredRunDeliveryDriver was exercised by exactly one crate-tier test
(triggered_approval_prompt_route_resolves_dm_approve_on_foreign_scope),
covering only the approval-gate path. Add the auth-gate twin: a
BlockedAuth triggered run whose auth-prompt preference resolves to the
creator's DM must carry the OAuth setup link
(triggered_auth_prompt_route_delivers_dm_setup_link_on_foreign_scope),
mirroring slack_dm_delivers_auth_prompt_with_setup_link_after_immediate_ack's
assertion shape but driven through the real triggered-delivery driver.

TriggeredRunDeliveryDriver only ever targets the creator's personal DM
(never a channel), so there is no literal "channel" arm to mirror
slack_channel_auth_prompt_omits_setup_link_after_immediate_ack. The
discriminating negative arm instead exercises the driver's own
send-time OAuth-DM backstop
(triggered_auth_prompt_oauth_target_not_dm_suppresses_setup_link_and_cancels_run):
when the resolved auth-prompt target is not a personal DM, the setup
link must never be posted and the blocked run must be cancelled
instead.

ScriptedTriggerCoordinator gains an additive
new_with_first_poll constructor (script an arbitrary first-poll
status/gate_ref instead of the hardcoded BlockedApproval/GATE pair)
and a functional cancel_run (previously unreachable!, since the
approval-only scenario never called it) to support the OAuth-not-DM
arm. Test code only; no production changes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(reborn): extract trigger-prompt materializer test-support helper (wave-4 lane C)

Committed follow-up on PR #5584's review thread: submit_triggered_turn_scripted
hand-mirrored ConversationContentRefMaterializer::materialize_prompt
(trigger_resolve_request + record_trigger_prompt + the content-ref shape,
field-by-field) instead of reusing it, and — as flagged — deliberately
SKIPPED authorize_trigger_fire and validate_trusted_trigger_prompt. Flagged
as a drift trap (trusted-trigger materialization is an ownership boundary,
AGENTS.md:61); the review agreed the fix is a #[cfg(feature = "test-support")]
materializer helper returning (TriggerMaterializedPrompt, TurnScope) living
beside the real materializer, held out of #5584 as a fast-follow with this
exact shape.

New production-crate (test-support-gated, compiles out of default builds)
surface in ironclaw_reborn_composition:
- trigger_poller_trusted_submit.rs: materialize_trigger_prompt_for_test,
  #[cfg(any(test, feature = "test-support"))] — runs the REAL production
  pipeline via ConversationContentRefMaterializer::materialize_prompt
  (authorize + validate + resolve + record + content-ref), then an
  idempotent second resolve_or_create_binding_with_trusted_scope call (safe
  — same request, same already-created binding) to also return the
  TurnScope the trait method computes internally but never exposes. Plus
  two crate-tier unit tests: positive (returned scope/content-ref match an
  independent ground-truth resolve) and negative (an unsafe prompt is
  rejected by the REAL safety validator).
- test_support/trigger_materializer.rs: pub, feature="test-support"-gated
  thin wrapper re-exported from test_support/mod.rs — the established
  wrap_project_create_capability_for_test-style pattern.

tests/support/reborn/triggered_submit.rs: submit_triggered_turn_scripted now
calls this ONE production-owned helper instead of hand-mirroring; deletes
~90 net lines of duplicated resolve/thread-record/content-ref logic.

Verified default-features build of ironclaw_reborn_composition stays
warning-free (function/import correctly compile out). Flip-checked at the
INTEGRATION level (not just the new crate-unit tests): forced an
injection-pattern prompt through submit_triggered_turn_scripted — every
triggered-gate scenario correctly failed with "rejected by safety scan",
proving the old hand-mirrored path's skip of validate_trusted_trigger_prompt
is now closed. Reverted before commit. All touched integration test bins
(reborn_group_triggers, reborn_integration_triggered_submit, plus every
other wave-4 lane-C bin) rerun green after the extraction.

* test(reborn): review fixes — consolidate slack e2e poll helpers, cite #5608 in fault-injection rationale

Factor the three near-identical bounded-poll-for-chat.postMessage
helpers in slack_serve/e2e_tests.rs into one predicate-parameterized
wait_for_post_messages_matching, and replace "Lane C final report"
citations with the filed issue (#5608) in the local-dev retry-path
rationale comments.

* test(reborn): address wave4 review comments

* test(reborn): relax auth gate harness wait

---------

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

This branch was successfully deployed

No deployments
ironclaw-ci-preview / ironclaw-pr-5067 — de663eff Deployed Jun 18, 2026 by railway-app[bot]
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: XL 500+ changed lines

Projects

None yet

2 participants