Skip to content

Record the in-flight ScriptTicket before the StartScript RPC

Placeholder ppxd requested to merge fix/start-script-record-before-rpc into main

Summary

  • DispatchOrReattachAsync recorded the in-flight ScriptTicket after StartScriptAsync returned (HalibutMachineExecutionStrategy.cs). A server crash — or a lost RPC response — in the window between the agent launching the script and the server committing the ticket left no durable pointer, so the resumed run dispatched a fresh ticket and ran the (non-idempotent) deployment script a second time.
  • Fix: record the ticket before firing StartScript (a one-statement reorder). A resume then always knows a ticket to re-probe — TryReattachAsync re-attaches if the agent still holds the script, or falls through to a clean fresh dispatch (via the agent's Complete+UnknownResult unknown-ticket sentinel) if it never started.

This is the StartScript half of the SOTA-audit P1 (server-side idempotency, aligning with Octopus ScriptServiceV2). The RPC-retry + observe-loop pointer-preservation half (GetStatus/CompleteScript) follows in a separate PR.

Why it's non-breaking + generic

  • Pure reorder of two adjacent calls that already exist; reuses the same IInFlightScriptStore and checkpoint column. No new state, no signature change.
  • It only ever avoids a duplicate — the agent is idempotent for a known ticket (LocalScriptService/ScriptPodService dedupe by ticket), so a retried/known StartScript returns status, not a second launch.
  • Composes with the existing reattach (#430 stripe-locking), checkpoint-preserving timeout (#428), and circuit breaker — it makes the reattach path the recovery mechanism, not a victim. If RecordDispatchedAsync itself fails, the dispatch now aborts cleanly before any agent-side launch (strictly safer than the old order).

Test plan

  • Integration regression HalibutResumeReattachTests.FreshDispatch_RecordsInFlightTicket_BeforeStartScriptRpc — a hook captures, at the instant StartScript fires, what the store has durably committed (read through a fresh DI scope, as a resumed server would). Verified red→green: fails with "record-before-RPC violated" before the reorder, passes after.
  • Checkpoints integration sweep: 12/12. Halibut/reattach unit: 158/158. dotnet build 0 errors.

Merge request reports

Loading