Skip to content

plan/start groundwork: defined wait-timeout semantics, soft-failing operations - #14082

Open
ndeloof wants to merge 2 commits into
mainfrom
plan-start-phase0
Open

plan/start groundwork: defined wait-timeout semantics, soft-failing operations#14082
ndeloof wants to merge 2 commits into
mainfrom
plan-start-phase0

Conversation

@ndeloof

@ndeloof ndeloof commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Phase 0 of #14081 (converge the start phase into the plan engine) — the two prerequisites, each useful on its own:

  1. waitDependencies no longer swallows its timeout (🐛 from Epic: make the codebase agent-legible — fix misleading self-description, ambiguous contracts, and legacy leftovers #14074 C): every polling goroutine returned nil on ctx.Done(), making the 'timeout waiting for dependencies' path unreachable except by race — up --wait --wait-timeout could succeed silently with nothing healthy. A deadline now surfaces as the failure it is; a plain user cancellation still returns nil so Ctrl-C doesn't masquerade as a dependency failure. Both paths gain their first unit tests.
  2. Walker-level Operation.Optional: a failing Optional node emits a Skipped event + warning and lets dependents run, instead of aborting the plan — the capability required: false dependency waits will need once they become plan nodes. Documented against the narrower pre-existing BestEffort flag so the two can't be confused. Unit-tested.

Part of #14081; also closes one 🐛 item of #14074 (section C).

🤖 Generated with Claude Code

waitDependencies returned nil when its context expired: every polling
goroutine hit the ctx.Done branch and reported success, so the
'timeout waiting for dependencies' translation after eg.Wait() was
unreachable in practice, and up --wait / start --wait could succeed
silently while nothing was healthy. The error path was only ever taken
by a race, when a health probe happened to be in flight at expiry.

The ctx.Done branch now distinguishes the two reasons the context can
end: a deadline is exactly the failure this function is asked to
detect and is surfaced (callers translate it into their own messages:
'timeout waiting for dependencies', 'application not healthy after X');
a plain cancellation (user interruption) keeps returning nil so
Ctrl-C does not masquerade as a dependency failure — preserving the
behavior of the restart/run/up callers that pass no timeout.

Both paths are now pinned by unit tests; neither message was covered
before.

Part of #14074 (C) and groundwork for the start-in-plan convergence.

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
…e waits

The executor aborted the whole plan on any node failure, which rules
out modeling optional semantics — a dependency declared with
required: false must be reported as skipped, not fail the operation,
and its dependents must still run. This is a prerequisite for planning
the start phase (dependency-condition waits) inside the reconciler.

Operation gains Optional: at the walker level, a failing Optional node
emits a Skipped event plus a warning and completes successfully, so
dependent nodes proceed. It is documented against the pre-existing
BestEffort flag, which is narrower (one specific expected error,
tolerated inside the operation itself) — the two docs now
cross-reference each other so they cannot be confused.

Covered by an executor unit test asserting a failing Optional node
does not fail the plan and its dependent still executes.

Groundwork for the start-in-plan convergence.

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
@ndeloof
ndeloof force-pushed the plan-start-phase0 branch from 585e8e3 to 2948f31 Compare August 18, 2026 09:22
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