Skip to content

orchestrate: resolvedRouting.fallback rejects an explicit null, and the spine's wording invites writing one #382

Description

@rodrigorjsf

Found at the very end of orchestrate run prd352-20260803-015333 — the render tools were the first thing to reject the checkpoint, after twelve slices had already been written with it.

Symptom

render_dashboard failed with RUN_STATE_INVALID on all twelve slices at once:

slices.353.resolvedRouting.fallback: Expected object, received null
slices.354.resolvedRouting.fallback: Expected object, received null
... (all 12)

render_graph and render_report fail identically, since they validate against the same schema.

Why the orchestrator wrote null

The spine describes the field as "the returned {model, variant, optional fallback}" and instructs the orchestrator to freeze that into resolvedRouting. For a slice with no configured fallback, null is the natural encoding of "there is no fallback" — and the sibling key fallbackTaken: false reads as its natural companion.

But the schema models "no fallback" as the key being absent, not as null. Nothing in the spine or in resolve_routing's output says so. resolve_routing itself returns fallbacks: [] — a plural array — while the checkpoint field is fallback, a singular optional object, so the orchestrator is already translating between two shapes with no stated mapping for the empty case.

Why it surfaced so late

validate_run_state is documented as the fast-fail guard — "call it right after writing the first run-state checkpoint and on every resume read, so a mis-shaped checkpoint fails in seconds rather than after expensive subagent work." That advice is sound and it would have caught this immediately.

But the spine only calls for it on resume, not after writing a slice's resolvedRouting on a fresh run. This run was a single fresh session, so it was never invoked until the end. Twelve slices' worth of work sat on an invalid checkpoint; only the fact that nothing else parses run-state.json through the schema — finalize_slice mutates raw JSON surgically — kept the run itself working.

Suggested fixes, in order of value

  1. Accept null as equivalent to absent (.nullish() rather than .optional()). An optional field that rejects an explicit null is a foot-gun in a file a model writes by hand, and nothing is gained by the strictness.
  2. State the empty-case encoding where the orchestrator reads its instructions — the spine says to freeze "the optional fallback" without saying what to write when there is none. Either form is fine once it is written down.
  3. Have the spine call validate_run_state after the first slice's resolvedRouting is written on a fresh run, not only on resume. The tool's own description already recommends exactly this; the spine only wires it into the resume path.

Workaround used

Deleted the fallback key wherever it was null, re-ran validate_run_state (valid), and re-rendered all three artifacts successfully. No slice work was affected.

Severity

Low for the run, higher for trust: the failure is late, loud and total — all three artifacts, all slices at once — and it is reachable by following the spine's own wording.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions