Skip to content

fix(engine): cascade on_error through Build-mode GC sweep#1999

Merged
georgeh0 merged 1 commit into
mainfrom
g/build-mode-on-error-cascade
May 20, 2026
Merged

fix(engine): cascade on_error through Build-mode GC sweep#1999
georgeh0 merged 1 commit into
mainfrom
g/build-mode-on-error-cascade

Conversation

@georgeh0
Copy link
Copy Markdown
Member

Summary

  • Resolves the spec's §4.2 gap: orphan-delete failures during a coco.mount-mounted parent's commit-phase GC sweep now route through the parent's exception handler chain (previously: error! log + swallow).
  • Symmetric with the existing ComponentDeleteContext::on_error cascade used by App.drop(): ComponentBuildContext gains the same field; launch_child_component_gc reads via a new unified processing_action_on_error() accessor.
  • One handler, two surfaces — the same on_error passed to Component::mount catches both the child's own task failure (existing) and orphan-delete failures from the child's commit (new). LiveComponentController::update_full / Op::Update mirror the same pattern. use_mount and root App::update pass None (preserves the prior log+swallow default by design).

Test plan

  • New test_orphan_delete_failure_routes_through_parent_handler in test_exception_handlers.py confirms the cascade.
  • Toggling launch_child_component_gc back to delete_action_on_error() confirms the test fails without the fix (seen == [] + framework's default error! print).
  • Full pytest python/ — 674 passed, 138 skipped, 0 failed locally.
  • cargo test, uv run mypy clean.
  • CI

🤖 Generated with Claude Code

Resolves the spec's §4.2 observability gap: previously, orphan-delete
failures during a normal update's commit phase (when a parent's
`process()` no longer declared a child it had declared on a prior run)
hit the framework's default `error!` log instead of routing through the
parent's user-installed exception handler chain. Only `App.drop()` (a
Delete-mode root with a raising on_error) propagated descendant
failures; `coco.mount`-mounted parents had no observability.

Symmetric with `ComponentDeleteContext::on_error`:

- `ComponentBuildContext` gains an `on_error: Option<OnError>` field.
- `ComponentProcessingAction::new_build` and
  `new_processor_context_for_build` accept it.
- `Component::mount` stores its `on_error` argument on the child's Build
  context (in addition to passing it to `run_in_background`) — one
  handler, two surfaces.
- `LiveComponentController::update_full` and `run_op`'s `Op::Update`
  branch mirror the same pattern for the live-component dispatch path.
- `App::update` (root), `Component::use_mount` (foreground), and
  `mount_inner_live`'s synthetic parent context pass `None` — the prior
  log+swallow default is preserved where intentional.

`launch_child_component_gc` switches from `delete_action_on_error()` to
the new unified `processing_action_on_error()`, so it cascades the
parent's on_error regardless of mode. `delete_action_on_error()` stays
as the Delete-only accessor used by `Component::delete`'s spawned task
(a Build context's on_error is meant for cascades, not for invoking on
the build's own failure — that flows through `run_in_background`'s
`on_error` argument directly).

Adds `test_orphan_delete_failure_routes_through_parent_handler` in
test_exception_handlers.py: parent mounts child A on update #1, doesn't
mount A on update #2, sink fails during the orphan delete. Verifies
the handler fires exactly once (mount_kind="mount"). Toggling
`launch_child_component_gc` back to `delete_action_on_error()`
confirms the test fails without this change.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@georgeh0 georgeh0 merged commit 75364a5 into main May 20, 2026
18 checks passed
@georgeh0 georgeh0 deleted the g/build-mode-on-error-cascade branch May 20, 2026 01:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant