Skip to content

refactor(errors): static enforcement that failures surface, not swallow - #5651

Merged
serrrfirat merged 2 commits into
mainfrom
static-error-surfacing-enforcement
Jul 8, 2026
Merged

serrrfirat merged 2 commits into
mainfrom
static-error-surfacing-enforcement

Conversation

@ilblackdragon

Copy link
Copy Markdown
Member

What & why

Follows the design discussion on #5383 (error-recoverability audit): can we make it a compile-time requirement that every error surfaces to the model/user instead of getting silently swallowed?

Full end-to-end delivery can't be proven by types (Rust has no effect/taint system), but misclassification and swallowing can be made build failures. This PR lands that enforcement, keystone-first.

Committed in this PR so far

Keystone — exhaustive error classification (no silent swallow):

  • Dropped #[non_exhaustive] from CapabilityFailureKind + RuntimeFailureKind (both already carry an Unknown open-set hatch, so the attribute was redundant belt-and-suspenders that only forced classifiers to keep a swallowing _ => arm).
  • Removed the wildcard arms from every fate-deciding classifier (capability_error_class, capability_failure_kind, generic_failure_recovery, runtime_failure_kind_to_loop). They're now exhaustive → a new named error variant fails to compile until deliberately classified. Behavior unchanged for existing variants.
  • Stopped propagating "unknown": deleted the dead internal RuntimeFailureKind::Unknown and collapsed the fail-safe redaction bucket RuntimeDispatchErrorKind::Unknown → RuntimeFailureKind::Internal (surfaces + retryable) instead of riding an opaque Unknown to a dedicated abort. Kept the outermost fail-safe redaction category and the string-carrying deserialization hatch.
  • Added every_capability_failure_kind_has_a_deliberate_recovery_class locking each variant's class and asserting only genuinely-terminal kinds may reach the run-aborting Permanent class.

Validated: cargo check --workspace --tests clean; touched-crate suites green (host_runtime 337, turns 295, loop_support 191, agent_loop + classification-lock test).

Still TODO in this PR (tracked; larger + higher-risk — see PR discussion)

  • RunFailureReason single funnel with a private constructor at the run boundary (planned_driver/turn_run_executor), so an unsurfaced terminal error becomes structurally unrepresentable. Major run-boundary change.
  • Boundary enforcement test — every reason resolves to SecurityStop only if from the safety/leak layer; else Retriable/Explainable with a non-empty user message.
  • Workspace swallow-idiom lints — unused_must_use / let_underscore_must_use / map_err_ignore. Measured blast radius is large (map_err(|_| ~861 sites), so the cleanup is a sweep, not a lint flip; scoping under discussion.

🤖 Generated with Claude Code

Make the capability-failure classification path fail-closed against the
"a new error kind silently aborts the run" class of bug (the keystone
finding of docs/plans/2026-06-28-reborn-error-recoverability-audit.md §6.1).

Static enforcement:
- Drop `#[non_exhaustive]` from `CapabilityFailureKind` and
  `RuntimeFailureKind`. Both already carry an open-set escape hatch
  (`Unknown`), so the attribute was redundant belt-and-suspenders whose
  only effect was to force classifiers to keep a wildcard `_ =>` arm that
  silently buckets any newly-added *named* variant.
- Remove the wildcard arms from every fate-deciding classifier
  (`capability_error_class`, `capability_failure_kind`,
  `generic_failure_recovery`, `runtime_failure_kind_to_loop`). They are
  now exhaustive, so a new named variant fails to compile until it is
  deliberately classified rather than defaulting into a run-aborting or
  wrong bucket. Behavior is unchanged for all existing variants.

Stop propagating "unknown":
- Delete `RuntimeFailureKind::Unknown` (internal, not serialized, and its
  only production source was a dead chain). Collapse the fail-safe
  redaction bucket `RuntimeDispatchErrorKind::Unknown` to
  `RuntimeFailureKind::Internal` in the dispatch->runtime `From` — an
  uncategorized dispatch error now surfaces as a retryable Internal
  failure instead of riding an opaque `Unknown` to a dedicated abort.
- Keep `RuntimeDispatchErrorKind::Unknown` as the outermost fail-safe
  redaction category (so redaction never fails closed) and
  `CapabilityFailureKind::Unknown(String)` as the string-carrying
  deserialization hatch.

Test: `every_capability_failure_kind_has_a_deliberate_recovery_class`
locks each variant's recovery class and asserts only genuinely-terminal
kinds may reach the run-aborting `Permanent` class (guards against silent
re-bucketing, complementing the compile-time exhaustiveness).

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

coderabbitai Bot commented Jul 4, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes
    • Improved and made deterministic failure classification by removing catch-all handling for both runtime and capability errors.
    • Reclassified unknown runtime dispatch errors as internal failures to reduce ambiguous “unknown” outcomes.
    • Updated recovery behavior and error-class mapping so “unknown” capability cases are handled consistently.
  • Refactor
    • Made runtime failure kind behavior fully explicit (including removing the public “Unknown” variant) and adjusted related tests to match the new classifications.

Walkthrough

Removes RuntimeFailureKind::Unknown and #[non_exhaustive], maps uncategorized runtime dispatch errors to Internal, and makes capability failure classification exhaustive with explicit Unknown(_) handling. CapabilityFailureKind is documented as the open-set escape hatch.

Changes

Exhaustive failure-kind classification

Layer / File(s) Summary
RuntimeFailureKind becomes exhaustive, Unknown variant removed
crates/ironclaw_host_runtime/src/lib.rs
#[non_exhaustive] and Unknown are removed from RuntimeFailureKind, and as_str is updated.
Dispatch error mapping collapses Unknown into Internal
crates/ironclaw_host_runtime/src/production.rs
RuntimeDispatchErrorKind::Unknown now maps to RuntimeFailureKind::Internal, and the pinned tests follow that mapping.
Loop-support mapping drops Unknown arm
crates/ironclaw_loop_support/src/capability_port.rs
runtime_failure_kind_to_loop removes the Unknown branch and fallback, with the test adjusted.
CapabilityFailureKind exhaustive matching in agent-loop
crates/ironclaw_agent_loop/src/executor/capability_helpers.rs, crates/ironclaw_agent_loop/src/executor/mapping.rs
Retry recovery and failure-class mappings stop using wildcard fallbacks, explicitly cover all CapabilityFailureKind variants, and add a table-driven exhaustiveness test.
CapabilityFailureKind declaration documents open-set escape hatch
crates/ironclaw_turns/src/run_profile/host.rs
#[non_exhaustive] is removed from CapabilityFailureKind, and the docs describe Unknown(...) as the forward-compatibility path.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

  • nearai/ironclaw#5051: Also changes crates/ironclaw_loop_support/src/capability_port.rs failure-kind translation behavior.
  • nearai/ironclaw#5296: Also tightens agent-loop capability failure classification after retry exhaustion.
  • nearai/ironclaw#5051: Also touches runtime-to-loop error category mapping.

Suggested reviewers: think-in-universe

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is detailed, but it omits most required template sections and checklists such as Linked Issue, Blast Radius, Rollback Plan, and Review Track. Rewrite the description to the repository template: add Summary bullets, Change Type, Linked Issue, Validation, Security Impact, Trust-Boundary, DB Impact, Blast Radius, Rollback, Review Follow-Through, and Review track.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed Conventional-commits style title clearly summarizes the error-classification refactor and matches the PR’s main change.
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.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5651 July 4, 2026 22:05 Destroyed
@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 Jul 4, 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 removes the #[non_exhaustive] attribute from RuntimeFailureKind and CapabilityFailureKind enums to enforce exhaustive compile-time matching. This ensures that any newly added variants must be explicitly classified rather than silently falling into wildcard fallback arms. Additionally, the RuntimeFailureKind::Unknown variant has been removed, with uncategorized dispatch errors now mapping to RuntimeFailureKind::Internal. Tests and mappings across the codebase have been updated accordingly. I have no further feedback to provide as there are no review comments.

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.

@github-actions

github-actions Bot commented Jul 4, 2026 •

Copy link
Copy Markdown
Contributor

Coverage ratchet

Ratchet mode: ENFORCING

RATCHET PASS: global
  observed: 85.32% (283704 / 332530 lines)
  floor:    85.3% (tolerance 0.5pp -> effective floor 84.8%)
  denominator: 332530 lines now vs 320188 at floor capture (+12342 lines, +3.85%) — not a material change

⚠️ 3 Reborn crate(s) have 0 int-tier coverage (target: 0) — ironclaw_prompt_envelope, ironclaw_scripts, ironclaw_skill_learning

Reborn integration-tier coverage

Line coverage (Reborn crates): 85.32% — 283704 / 332530 lines

Per-crate breakdown (65 crates, lowest-covered first)
Crate Line % Covered / Total
ironclaw_prompt_envelope 0% 0 / 88
ironclaw_scripts 0% 0 / 347
ironclaw_skill_learning 0% 0 / 61
ironclaw_wasm_sandbox_core 7.37% 7 / 95
ironclaw_runtime_policy 33.2% 80 / 241
ironclaw_event_projections 43.34% 673 / 1553
ironclaw_run_state 52.36% 222 / 424
ironclaw_authorization 53.54% 461 / 861
ironclaw_triggers 59.99% 1736 / 2894
ironclaw_observability 61.54% 16 / 26
ironclaw_webui_v2 62.98% 2528 / 4014
ironclaw_mcp 63.15% 581 / 920
ironclaw_reborn_cli 64.5% 3999 / 6200
ironclaw_reborn_migration 67.01% 1172 / 1749
ironclaw_memory 67.12% 747 / 1113
ironclaw_dispatcher 67.15% 92 / 137
ironclaw_filesystem 67.44% 3815 / 5657
ironclaw_trust 72.88% 661 / 907
ironclaw_capabilities 74.08% 1658 / 2238
ironclaw_wasm_limiter 74.6% 47 / 63
ironclaw_reborn_event_store 74.61% 958 / 1284
ironclaw_extractors 74.72% 538 / 720
ironclaw_first_party_extensions 77.62% 5410 / 6970
ironclaw_llm 77.88% 19479 / 25013
ironclaw_product_context 78.57% 11 / 14
ironclaw_wasm_product_adapters 80.58% 1510 / 1874
ironclaw_process_sandbox 80.65% 671 / 832
ironclaw_reborn_openai_compat 80.95% 956 / 1181
ironclaw_memory_native 81.86% 3226 / 3941
ironclaw_wasm 82.54% 950 / 1151
ironclaw_secrets 82.7% 2791 / 3375
ironclaw_events 83.47% 1762 / 2111
ironclaw_processes 84.06% 965 / 1148
ironclaw_turns 84.28% 13083 / 15523
ironclaw_host_api 84.8% 3131 / 3692
ironclaw_product_workflow 85.88% 10818 / 12597
ironclaw_projects 85.92% 659 / 767
ironclaw_network 86.12% 670 / 778
ironclaw_auth 86.32% 2727 / 3159
ironclaw_threads 86.33% 4015 / 4651
ironclaw_common 86.59% 1472 / 1700
ironclaw_slack_v2_adapter 86.79% 1806 / 2081
ironclaw_reborn_config 86.98% 1730 / 1989
ironclaw_reborn_identity 87.03% 557 / 640
ironclaw_product_adapters 87.29% 3207 / 3674
ironclaw_skills 87.37% 4337 / 4964
ironclaw_hooks 87.84% 9916 / 11289
ironclaw_product_adapter_registry 87.96% 526 / 598
ironclaw_reborn_traces 88.23% 11707 / 13268
ironclaw_extensions 88.26% 2631 / 2981
ironclaw_host_runtime 88.92% 17477 / 19655
ironclaw_reborn_composition 88.96% 69289 / 77892
ironclaw_conversations 90% 2924 / 3249
ironclaw_approvals 90.51% 1507 / 1665
ironclaw_reborn 91.23% 17562 / 19251
ironclaw_event_streams 91.48% 1009 / 1103
ironclaw_loop_support 92.22% 14226 / 15426
ironclaw_resources 93.05% 4607 / 4951
ironclaw_attachments 93.06% 630 / 677
ironclaw_reborn_webui_ingress 93.19% 2217 / 2379
ironclaw_telegram_v2_adapter 94.01% 2447 / 2603
ironclaw_agent_loop 94.58% 8776 / 9279
ironclaw_safety 94.81% 3669 / 3870
ironclaw_first_party_extension_ports 95% 3094 / 3257
ironclaw_outbound 95.59% 3556 / 3720

This table itself is informational and never gates the PR on its own — not the percentage, not the per-crate holes, not the 0-coverage callout. A separate coverage ratchet (dry-run until enforce=true; see tests/integration/coverage-floor.toml) can fail the build on specific configured floors.

Exemptions (4 entry/entries excluded from the accounting above)
Module / Crate Reason Issue
crate: ironclaw_embeddings v1-only: consumed only by root ironclaw (src/app.rs, src/tools/builtin/memory.rs, src/workspace/mod.rs, src/config/{mod,embeddings}.rs); no crates/* dependents. Covered by "Tests (Legacy)". #5657
crate: ironclaw_gateway v1-only: consumed only by root ironclaw (src/channels/web/platform/static_files.rs, src/channels/web/handlers/frontend.rs); no crates/* dependents. Covered by "Tests (Legacy)". #5657
crate: ironclaw_oauth v1-only: consumed only by root ironclaw (src/auth/oauth.rs); no crates/* dependents. Crate's own doc comment confirms v1-only. Covered by "Tests (Legacy)". #5657
crate: ironclaw_tui v1-only: consumed only by root ironclaw (src/main.rs, src/channels/tui.rs); no crates/* dependents. Crate's own doc comment confirms it bridges INTO v1, not Reborn. Covered by "Tests (Legacy)". #5657

@railway-app

railway-app Bot commented Jul 4, 2026

Copy link
Copy Markdown

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

Service Status Web Updated (UTC)
ironclaw ✅ Success (View Logs) Web Jul 4, 2026 at 10:13 pm

@ilblackdragon
ilblackdragon marked this pull request as ready for review July 4, 2026 22:24
Copilot AI review requested due to automatic review settings July 4, 2026 22:24

Copilot AI 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.

Pull request overview

This PR strengthens “no silent swallow” guarantees in the Reborn error pipeline by removing #[non_exhaustive] + wildcard match arms in fate-deciding classifiers, making new error variants a compile-time forcing function for deliberate classification/recovery behavior.

Changes:

  • Removed #[non_exhaustive] and wildcard _ => arms across failure-kind classifiers to enforce exhaustive matching at compile time.
  • Collapsed dispatch’s redaction bucket (RuntimeDispatchErrorKind::Unknown) into RuntimeFailureKind::Internal, and removed the dead RuntimeFailureKind::Unknown variant.
  • Added a classification-locking test to pin CapabilityFailureKind -> CapabilityErrorClass mappings and detect re-bucketing regressions.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
crates/ironclaw_turns/src/run_profile/host.rs Drops #[non_exhaustive] on CapabilityFailureKind and documents the “Unknown-value” escape hatch to keep classifiers exhaustive.
crates/ironclaw_loop_support/src/capability_port.rs Makes runtime_failure_kind_to_loop exhaustive by removing Unknown/wildcard mapping paths and updates tests accordingly.
crates/ironclaw_host_runtime/src/production.rs Maps RuntimeDispatchErrorKind::Unknown to RuntimeFailureKind::Internal and updates pinning tests for the new behavior.
crates/ironclaw_host_runtime/src/lib.rs Removes #[non_exhaustive] and the Unknown variant from RuntimeFailureKind; keeps the enum match surfaces exhaustive.
crates/ironclaw_agent_loop/src/executor/mapping.rs Removes wildcard classification fallback and adds a regression test that locks recovery class per CapabilityFailureKind variant.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 696 to +703
/// Stable, sanitized failure categories.
///
// Deliberately NOT `#[non_exhaustive]`: the `Unknown` variant is the open-set
// escape hatch for unrecognized runtime failures, so the attribute would only
// force classifiers to keep a wildcard arm that silently buckets a new named
// variant. Without it, disposition/classification matches are exhaustive and a
// new named variant fails to compile until classified. See
// `docs/plans/2026-06-28-reborn-error-recoverability-audit.md` §6.1.

@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_host_runtime/src/lib.rs`:
- Around line 697-703: Update the comment attached to RuntimeFailureKind so it
no longer references an Unknown variant that was removed; the current rationale
is incorrect and belongs to CapabilityFailureKind. Keep only the valid
explanation for omitting #[non_exhaustive] in this enum, using the
RuntimeFailureKind symbol to locate the block, and ensure the comment reflects
that matches remain exhaustive and new variants fail to compile until
classified. Also consider removing the comment entirely if the remaining
behavior is obvious enough to satisfy the Rust commenting guideline.
🪄 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: 8487d6f4-b705-4267-bbd8-c67dce48cb17

📥 Commits

Reviewing files that changed from the base of the PR and between 3c54f13 and 5b1fa15.

📒 Files selected for processing (6)
  • crates/ironclaw_agent_loop/src/executor/capability_helpers.rs
  • crates/ironclaw_agent_loop/src/executor/mapping.rs
  • crates/ironclaw_host_runtime/src/lib.rs
  • crates/ironclaw_host_runtime/src/production.rs
  • crates/ironclaw_loop_support/src/capability_port.rs
  • crates/ironclaw_turns/src/run_profile/host.rs
💤 Files with no reviewable changes (2)
  • crates/ironclaw_agent_loop/src/executor/capability_helpers.rs
  • crates/ironclaw_loop_support/src/capability_port.rs

Comment on lines +697 to +703
///
// Deliberately NOT `#[non_exhaustive]`: the `Unknown` variant is the open-set
// escape hatch for unrecognized runtime failures, so the attribute would only
// force classifiers to keep a wildcard arm that silently buckets a new named
// variant. Without it, disposition/classification matches are exhaustive and a
// new named variant fails to compile until classified. See
// `docs/plans/2026-06-28-reborn-error-recoverability-audit.md` §6.1.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Comment contradicts the enum it documents. RuntimeFailureKind no longer has an Unknown variant (this PR removed it), yet the block asserts "the Unknown variant is the open-set escape hatch for unrecognized runtime failures." That rationale belongs to CapabilityFailureKind, which keeps Unknown(_); here it's just wrong and will mislead the next reader. The valid reason for dropping #[non_exhaustive] is only the "matches stay exhaustive, new variants fail to compile until classified" clause.

📝 Suggested rewrite
-///
-// Deliberately NOT `#[non_exhaustive]`: the `Unknown` variant is the open-set
-// escape hatch for unrecognized runtime failures, so the attribute would only
-// force classifiers to keep a wildcard arm that silently buckets a new named
-// variant. Without it, disposition/classification matches are exhaustive and a
-// new named variant fails to compile until classified. See
-// `docs/plans/2026-06-28-reborn-error-recoverability-audit.md` §6.1.
+///
+// Deliberately NOT `#[non_exhaustive]` and intentionally has no open-set
+// `Unknown` variant: uncategorized dispatch errors collapse to `Internal`.
+// Without the attribute, disposition/classification matches stay exhaustive and
+// a new named variant fails to compile until it is explicitly classified. See
+// `docs/plans/2026-06-28-reborn-error-recoverability-audit.md` §6.1.

As per coding guidelines: "Add comments only for non-obvious logic in Rust code."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
///
// Deliberately NOT `#[non_exhaustive]`: the `Unknown` variant is the open-set
// escape hatch for unrecognized runtime failures, so the attribute would only
// force classifiers to keep a wildcard arm that silently buckets a new named
// variant. Without it, disposition/classification matches are exhaustive and a
// new named variant fails to compile until classified. See
// `docs/plans/2026-06-28-reborn-error-recoverability-audit.md` §6.1.
///
// Deliberately NOT `#[non_exhaustive]` and intentionally has no open-set
// `Unknown` variant: uncategorized dispatch errors collapse to `Internal`.
// Without the attribute, disposition/classification matches stay exhaustive and
// a new named variant fails to compile until it is explicitly classified. See
// `docs/plans/2026-06-28-reborn-error-recoverability-audit.md` §6.1.
🤖 Prompt for 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.

In `@crates/ironclaw_host_runtime/src/lib.rs` around lines 697 - 703, Update the
comment attached to RuntimeFailureKind so it no longer references an Unknown
variant that was removed; the current rationale is incorrect and belongs to
CapabilityFailureKind. Keep only the valid explanation for omitting
#[non_exhaustive] in this enum, using the RuntimeFailureKind symbol to locate
the block, and ensure the comment reflects that matches remain exhaustive and
new variants fail to compile until classified. Also consider removing the
comment entirely if the remaining behavior is obvious enough to satisfy the Rust
commenting guideline.

Source: Coding guidelines

…error-surfacing

# Conflicts:
#	crates/ironclaw_agent_loop/src/executor/mapping.rs
Copilot AI review requested due to automatic review settings July 8, 2026 12:27

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review is ineligible. To be eligible to request a review, you need a paid Copilot license, or your organization must enable Copilot code review.

@ironloopai

ironloopai Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

⏳ IronLoop Review Status

Head: 1bbf7635e4f779abb3b1cac4d2ab4d65b8c0ef72
Result: No reviewer jobs are scheduled yet.
Next: Run @ironloopai review to start reviewers.
Updated: 2026-07-08T12:27:10.855Z

Current reviewers:

Reviewer State Verdict Findings Last update
none Queued N/A No reviewer jobs scheduled yet. N/A
Reviewer summaries
Reviewer Detail
none No reviewer jobs scheduled yet.
Recent activity
Time Reviewer State Detail
N/A N/A Waiting No progress events recorded yet.
Available commands
  • @ironloopai help
  • @ironloopai agents
  • @ironloopai review
  • @ironloopai review --agent <agent-id-or-alias>
  • @ironloopai status
Run metadata

Admission: webhook accepted the request and IronLoop persisted review state before this projection.

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5651 July 8, 2026 12:27 Destroyed

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
crates/ironclaw_host_runtime/src/lib.rs (1)

727-746: 🩺 Stability & Availability | 🔵 Trivial

Removing the "unknown" tracing/metric token, not renaming it, is an observability contract change.

Per the pinning test comment elsewhere ("Pin the public metric/tracing tokens; renaming any of these is a breaking observability contract change"), any dashboard/alert filtering on runtime_failure_kind="unknown" will silently stop matching once unclassified dispatch errors report as "internal" instead. Worth a release note / dashboard sweep before this ships.

🤖 Prompt for 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.

In `@crates/ironclaw_host_runtime/src/lib.rs` around lines 727 - 746, The
observability token set is changing in `as_str` on the runtime failure kind
enum, and dropping the `"unknown"` token is a breaking contract. Keep
`"unknown"` available as a stable tracing/metric value (or add an explicit
compatibility mapping alongside the new `Internal` variant) so existing
dashboards and alerts keep matching, and if the rename is intentional, update
any consumers and release notes accordingly.
crates/ironclaw_host_runtime/src/production.rs (1)

937-959: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Settle the blocked resume record here
resume_spawn_capability already settles every other preflight failure through fail_matching_blocked_resume_on_preflight_error(...); this ModelInputRejected early return skips that path, so the BlockedApproval record for this approval_request_id can remain stale.

🤖 Prompt for 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.

In `@crates/ironclaw_host_runtime/src/production.rs` around lines 937 - 959, The
ModelInputRejected branch in resume_spawn_capability is returning early without
settling the blocked resume record, leaving the BlockedApproval for
approval_request_id stale. Route this failure through the same preflight cleanup
path used elsewhere in resume_spawn_capability by calling
fail_matching_blocked_resume_on_preflight_error(...) before returning the Failed
outcome, so the blocked resume state is always resolved consistently.
🤖 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.

Outside diff comments:
In `@crates/ironclaw_host_runtime/src/lib.rs`:
- Around line 727-746: The observability token set is changing in `as_str` on
the runtime failure kind enum, and dropping the `"unknown"` token is a breaking
contract. Keep `"unknown"` available as a stable tracing/metric value (or add an
explicit compatibility mapping alongside the new `Internal` variant) so existing
dashboards and alerts keep matching, and if the rename is intentional, update
any consumers and release notes accordingly.

In `@crates/ironclaw_host_runtime/src/production.rs`:
- Around line 937-959: The ModelInputRejected branch in resume_spawn_capability
is returning early without settling the blocked resume record, leaving the
BlockedApproval for approval_request_id stale. Route this failure through the
same preflight cleanup path used elsewhere in resume_spawn_capability by calling
fail_matching_blocked_resume_on_preflight_error(...) before returning the Failed
outcome, so the blocked resume state is always resolved consistently.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: a96086ea-8cbd-44cd-b5e8-acaa457a6ee4

📥 Commits

Reviewing files that changed from the base of the PR and between 5b1fa15 and 1bbf763.

📒 Files selected for processing (5)
  • crates/ironclaw_agent_loop/src/executor/capability_helpers.rs
  • crates/ironclaw_agent_loop/src/executor/mapping.rs
  • crates/ironclaw_host_runtime/src/lib.rs
  • crates/ironclaw_host_runtime/src/production.rs
  • crates/ironclaw_loop_support/src/capability_port.rs

@serrrfirat
serrrfirat merged commit 6ad8bce into main Jul 8, 2026
62 checks passed
@serrrfirat
serrrfirat deleted the static-error-surfacing-enforcement branch July 8, 2026 12:42
BenKurrek added a commit that referenced this pull request Jul 8, 2026
…eshold

Reborn Playwright and IronClaw Stress were failing nightly with no
alerting at all — same silent-failure class the deep-CI revival fixed,
in two more places. Diagnosis of the standing failures:

- IronClaw Stress (red every retained scheduled run): the nightly
  bottleneck suite caps p95 at 1500ms, but its model-tail case injects
  a synthetic 2.0s model wait by design — the ceiling was structurally
  unsatisfiable (measured p95 2.05s = the 2.0s wait + ~50ms real work;
  every case at 0.00% failures). Raised to 2500ms with a comment; the
  PR-mode variant already ran at 3000ms. Per-case ceilings in
  ironclaw_stress are the proper follow-up.
- Reborn Playwright (flaky-red): last night's failure was
  test_reborn_legacy_looping_tool_calls_stop_at_low_iteration_boundary
  asserting failure_category == "driver_protocol_violation" while the
  runtime now emits "iteration_limit" — already realigned on main by
  the error-classification refactor (#5651); no change needed here.

Alerting changes:

- ironclaw-stress.yml + reborn-playwright.yml: schedule-only alert jobs
  driving .github/scripts/nightly-alert-issue.sh, same contract as the
  Nightly E2E / Nightly Deep CI alerts.
- nightly-watchdog.yml: generalized to a matrix over all four nightlies
  (Deep CI, E2E, Playwright, Stress) — startup failures and
  cron-never-fired now alarm for every nightly, not just Deep CI.
- nightly-alert-issue.sh: optional Slack mirror. When the
  SLACK_CI_ALERTS_WEBHOOK_URL repo secret is set (Slack incoming
  webhook), every failure posts a one-liner with run + issue links and
  every recovery posts a close-out; absent secret = silent no-op.
  Delivery is best-effort and never fails the alert job. All five call
  sites pass the secret through.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
BenKurrek added a commit that referenced this pull request Jul 8, 2026
…E2E requirable (#5841)

* ci: revive the nightly deep tier + make Platform & Compat and Reborn E2E requirable

Nightly Deep CI has startup-failed every night since 2026-06-20 with
zero jobs: reborn-tests.yml references secrets.SCCACHE_* (sccache-dist)
and nightly's call site did not pass secrets, which fails workflow
validation at trigger time. The in-run nightly-alert job dies with the
run, so nothing reported it. Separately, Platform & Compat's deep jobs
(Windows build, bench compile, docker build) were gated on
github.event_name == 'workflow_call', which never matches — in a
reusable workflow event_name reflects the caller's event — so nightly's
"deep reuse" of those jobs silently skipped (all three are 'skipped' in
the last nightly run that executed jobs at all).

- nightly-deep-ci.yml: pass `secrets: inherit` to the reborn-tests call
- platform-and-compat.yml: add a `deep` workflow_call marker input
  (default true, materializes only under workflow_call) and gate
  windows-build / wasm-wit-compat / bench-compile / docker-build on it
  instead of the never-true event_name comparison
- nightly-watchdog.yml (new): dead-man's switch that inspects the
  latest scheduled Nightly Deep CI run from outside it — startup
  failures and never-fired crons now raise/update the same "Nightly
  Deep CI failed" issue via .github/scripts/nightly-alert-issue.sh
- platform-and-compat.yml: add a stable "Platform & Compat" roll-up job
  (skip-tolerant, requirable as a status check), delete the vestigial
  matrix-config job (test_matrix had no consumer; windows_matrix's SLIM
  branch was unreachable because windows-build never runs on PR or
  merge_group), and build Docker images in the merge queue when the
  merge group touches Dockerfile inputs
- reborn-e2e.yml: run in the merge queue — merge_group trigger plus a
  changes job mirroring the pull_request/push paths filters
  (merge_group does not support paths), with the "Reborn E2E" roll-up
  reporting on every queue entry so it can become a required check
- .github/workflows/README.md (new): the CI tier contract,
  required-check inventory, deep-tier gotchas, and deliberately
  accepted gaps

Verified with actionlint (no findings beyond pre-existing SC2129 style
nits). Reborn E2E queue cost is ~5-9 min based on recent main runs.

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

* ci(nightly): grant caller-side permissions the called workflows require

A dispatched validation run of the previous commit still startup-failed:
secrets: inherit fixed one violation, but called-workflow jobs that
declare job-level permissions beyond the caller's grant also fail
validation at trigger time (even when those jobs are event-gated off
schedule runs). platform-and-compat's version-check declares
pull-requests/issues read; reborn-tests' coverage-report declares
pull-requests: write. Grant those supersets at the call sites.

Also corrects the incident window in the comments and README: retained
history shows zero successful Nightly Deep CI runs since its creation
on 2026-05-06 (65 of 74 runs are startup_failures), not merely since
2026-06-20 — the permissions violations date to day one, the secrets
one to the 2026-07-03 sccache rollout.

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

* ci(nightly): alert every nightly, mirror to Slack, fix the stress threshold

Reborn Playwright and IronClaw Stress were failing nightly with no
alerting at all — same silent-failure class the deep-CI revival fixed,
in two more places. Diagnosis of the standing failures:

- IronClaw Stress (red every retained scheduled run): the nightly
  bottleneck suite caps p95 at 1500ms, but its model-tail case injects
  a synthetic 2.0s model wait by design — the ceiling was structurally
  unsatisfiable (measured p95 2.05s = the 2.0s wait + ~50ms real work;
  every case at 0.00% failures). Raised to 2500ms with a comment; the
  PR-mode variant already ran at 3000ms. Per-case ceilings in
  ironclaw_stress are the proper follow-up.
- Reborn Playwright (flaky-red): last night's failure was
  test_reborn_legacy_looping_tool_calls_stop_at_low_iteration_boundary
  asserting failure_category == "driver_protocol_violation" while the
  runtime now emits "iteration_limit" — already realigned on main by
  the error-classification refactor (#5651); no change needed here.

Alerting changes:

- ironclaw-stress.yml + reborn-playwright.yml: schedule-only alert jobs
  driving .github/scripts/nightly-alert-issue.sh, same contract as the
  Nightly E2E / Nightly Deep CI alerts.
- nightly-watchdog.yml: generalized to a matrix over all four nightlies
  (Deep CI, E2E, Playwright, Stress) — startup failures and
  cron-never-fired now alarm for every nightly, not just Deep CI.
- nightly-alert-issue.sh: optional Slack mirror. When the
  SLACK_CI_ALERTS_WEBHOOK_URL repo secret is set (Slack incoming
  webhook), every failure posts a one-liner with run + issue links and
  every recovery posts a close-out; absent secret = silent no-op.
  Delivery is best-effort and never fails the alert job. All five call
  sites pass the secret through.

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

* ci(nightly): Slack-only failure alerting through a single watchdog path

Per review direction: failures post to Slack, nothing else, one code
path, no GitHub issues.

- nightly-watchdog.yml is now the only alerting mechanism: at 08:00 UTC
  it checks each nightly's latest scheduled run (Nightly Deep CI,
  Nightly E2E, Reborn Playwright, IronClaw Stress) and posts failures —
  workflow, conclusion, failed job names, run link — to the Slack
  channel behind the existing secrets.SLACK_WEBHOOK_URL (the same
  webhook live-canary reports through; no new secret needed). Missing
  runs, stale runs (>26h, cron never fired), and startup_failures alarm
  too — the cases an in-run alert job structurally cannot see. A
  detected failure turns the watchdog matrix job red so its run history
  doubles as the failure record. Successes post nothing.
- Removed the GitHub-issue alerting entirely: nightly-alert-issue.sh
  and its test harness are deleted, and the in-run nightly-alert jobs
  are removed from nightly-deep-ci.yml and nightly-e2e.yml along with
  the round-2 stress/playwright alert jobs.

Housekeeping after merge: close the open "Nightly E2E failed" issue
(#4108) manually — nothing auto-closes it now.

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

* ci: address review — merge-queue docker gate + drop dead scope output

- docker-build: the merge_group arm no longer requires has_legacy_tests.
  The scope classifier matches only the literal `Dockerfile`, so a
  Dockerfile.reborn/.dockerignore-only merge group reported
  has_legacy_tests=false and skipped the Docker build exactly when it
  should run (IronLoop blocking finding). push/deep arms keep the gate.
- changes: drop has_engine_replay_risk — no consumer in this workflow,
  and its workflow_call arm could never fire (github.event_name is the
  caller's event in reusable workflows).

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

* ci(nightly): freeze the legacy v1 suite — do not invoke test.yml from nightly

Deliberate freeze pending v1 (src/) removal, per team decision: nightly
no longer calls the Legacy Tests workflow, leaving test.yml invoked
nowhere. Documented loudly in the workflow header and the CI README —
including the consequence that test.yml is the only place the root
`ironclaw` package's tests run, and that a v1 fix landing before src/
is deleted should temporarily restore the call job. This is an explicit
freeze with a paper trail, not the silent-death mode this workflow's
history is infamous for; delete test.yml together with src/.

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

* ci(nightly): remove the v1 browser suite from the nightly fleet

Completes the legacy freeze: nightly-e2e.yml (the scheduler for
e2e.yml's full v1 browser suite) is deleted and Nightly E2E is dropped
from the watchdog matrix. Like Nightly Deep CI before its revival, it
had zero successful runs in retained history — its own alert issue
notes there is no prior green run on main to attribute against — and
its standing failures (v1 /api/chat auth-gate SSE dedup and approval
tests) are v1 work the team has frozen pending src/ removal.

e2e.yml itself stays (workflow_call/workflow_dispatch), frozen
alongside test.yml; both are documented in the CI README to be deleted
together with src/. The nightly fleet is now Nightly Deep CI, Reborn
Playwright, and IronClaw Stress — all three validated green today —
with the watchdog covering exactly those three.

After merge: manually close the open "Nightly E2E failed" issue #4108
(the freeze resolves it; nothing auto-closes it).

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

* docs(ci): record the full-path Emulate coverage gap left by the v1 freeze

The Emulate-backed full-path tests are the only scheduled coverage for
install -> OAuth -> model-routed tool call -> provider mutation, and per
tests/e2e/CLAUDE.md they boot the legacy gateway binary, so they froze
with v1. Note in the CI README that a Reborn-native port through
`ironclaw-reborn serve` is the follow-up that restores this tier —
deliberately NOT re-homed into the Reborn nightlies as-is, which would
have smuggled the legacy binary back in.

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
ilblackdragon added a commit that referenced this pull request Jul 9, 2026
#5652)

A discarded `Result` / `#[must_use]` value is a swallowed error. This
promotes the warn-by-default `unused_must_use` to a workspace-wide deny,
so a dropped `Result` fails the build instead of silently hiding a
failure. Verified zero current fires across `cargo check --workspace
--tests`, so this changes no existing code — it only guards against
*future* silent drops.

Companion swallow-idiom lints are deferred to their own PRs because each
needs a real cleanup first (measured on this tree):
  - clippy::let_underscore_must_use — 67 `let _ = <must_use>` sites
    (mix of safe discards and genuine swallows, e.g. an ignored async
    delete); repo treats clippy warnings as errors, so enabling it
    requires fixing all 67.
  - clippy::map_err_ignore — ~861 sites; a blanket deny is wrong since
    error-handling.md permits map_err to a specific typed error and only
    forbids cause-dropping ones.

Part of the error-surfacing enforcement effort (keystone: #5651).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: firat.sertgoz <firat.sertgoz@near.ai>
ilblackdragon added a commit that referenced this pull request Jul 10, 2026
…_ = drops

Convert 90 `let _ = <fallible>` sites — where a `Result`/`#[must_use]` was
silently discarded — into one of two explicit forms, across the runtime
error paths that matter most (host_runtime, reborn_composition, reborn,
hooks, plus filesystem/common/openai_compat stragglers):

- Real fallible best-effort ops (temp-file/secret/manifest cleanup,
  child.kill/.wait, event emit, service teardown, scheduler nudges) now
  SURFACE their error to a `tracing::debug!` line instead of swallowing it,
  so a failed cleanup is diagnosable. `debug!` only — never info!/warn!,
  which corrupt the REPL/TUI.
- Genuinely-intentional discards (infallible `write!` to a String,
  fire-and-forget oneshot sends where a dropped receiver is expected,
  detached-thread join results, `OnceLock::set` idempotency) keep `let _ =`
  but carry an explicit `#[allow(clippy::let_underscore_must_use)]` with a
  one-line reason stating why discarding is correct.

This does NOT enable a workspace-wide `let_underscore_must_use = deny`: a
fresh full-workspace clippy shows ~998 fires across ~29 crates, the large
majority benign intentional discards, so blanket enforcement is
disproportionate churn. The static no-swallow *enforcement* is already
delivered by the exhaustive-classification keystone (#5651) and the
`unused_must_use` deny (#5652); this PR is the targeted surfacing cleanup
for the highest-value runtime paths.

Verified: touched-crate suites green (2020 tests in the default-feature
group; reborn_composition green under its full feature set).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ilblackdragon added a commit that referenced this pull request Jul 10, 2026
…_ drops (90 sites) (#5662)

* refactor(errors): surface best-effort failures instead of silent let _ = drops

Convert 90 `let _ = <fallible>` sites — where a `Result`/`#[must_use]` was
silently discarded — into one of two explicit forms, across the runtime
error paths that matter most (host_runtime, reborn_composition, reborn,
hooks, plus filesystem/common/openai_compat stragglers):

- Real fallible best-effort ops (temp-file/secret/manifest cleanup,
  child.kill/.wait, event emit, service teardown, scheduler nudges) now
  SURFACE their error to a `tracing::debug!` line instead of swallowing it,
  so a failed cleanup is diagnosable. `debug!` only — never info!/warn!,
  which corrupt the REPL/TUI.
- Genuinely-intentional discards (infallible `write!` to a String,
  fire-and-forget oneshot sends where a dropped receiver is expected,
  detached-thread join results, `OnceLock::set` idempotency) keep `let _ =`
  but carry an explicit `#[allow(clippy::let_underscore_must_use)]` with a
  one-line reason stating why discarding is correct.

This does NOT enable a workspace-wide `let_underscore_must_use = deny`: a
fresh full-workspace clippy shows ~998 fires across ~29 crates, the large
majority benign intentional discards, so blanket enforcement is
disproportionate churn. The static no-swallow *enforcement* is already
delivered by the exhaustive-classification keystone (#5651) and the
`unused_must_use` deny (#5652); this PR is the targeted surfacing cleanup
for the highest-value runtime paths.

Verified: touched-crate suites green (2020 tests in the default-feature
group; reborn_composition green under its full feature set).

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

* fix: address cleanup review feedback

* fix: address review feedback on best-effort failure surfacing

Follow-up to the let-underscore refactor, addressing actionable review
threads:

- llm_config_service: delete_provider now fails closed. The stored key
  is deleted *before* the provider definition (delete is idempotent), so
  a key-cleanup failure returns an error with the definition intact
  rather than reporting deletion success while orphaning a secret.
  Regression test added.
- extension_lifecycle: lifecycle-disable and manifest rollback failures
  during activation/install compensation now propagate through the
  existing compensation_failure path instead of being debug-logged, so a
  compound failure surfaces the orphaned state (fail loud) rather than
  silently poisoning future retries. Single-fault and happy paths are
  unchanged (covered by existing tests).
- shell.rs: extracted the duplicated saved-output cleanup-and-log block
  into a single closure, matching the sibling trace_commons.rs fix.
- wasm/runtime.rs: log the epoch-ticker join panic at debug on drop
  instead of a pure discard, dropping the let_underscore_must_use allow.
- turn_scheduler tests: assert the first enqueue succeeds to establish
  the saturated-queue precondition before matching DeliveryUnavailable.

Already addressed on-branch and verified: SecretStore cleanup logs use
stable_reason(), purge_secret_handle consolidates the six durable
product-auth cleanup sites, and trace_commons.rs has the cleanup_temp
helper.

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

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

This branch was successfully deployed

No deployments
ironclaw-ci-preview / ironclaw-pr-5651 — 1bbf7635 Deployed Jul 8, 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: M 50-199 changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants