senro rerun

Re-execute the plan a previous run recorded.

senro rerun [--run RUN] [--step STEP] [--regenerate] [--dir DIR]
            [--cache-dir DIR] [--local-class CLASS]

Reach for this when you need to reproduce a past run exactly: replaying a failure to debug it, or re-executing just one step and everything that depends on it, rather than starting a fresh run from source.

senro rerun reads <run>/plan.json, not your pipeline package. That’s intentional: rebuilding the pipeline would re-resolve its definition, which might have changed since the original run, giving you a different plan and a different run. Reading the recorded plan instead means a re-run repeats the original run rather than re-discovering a new one. It also means no Go toolchain or pipeline source is needed.

Steps whose inputs haven’t changed are served from the action cache, so an unchanged re-run is mostly cache hits.

Flags

FlagMeaning
--run RUNThe run to re-execute. Defaults to the most recent one here.
--step STEPRe-execute this step, what it needs, and everything below it.
--regenerateAsk generators for a fresh subgraph instead of replaying the recorded one.
--dir DIRWhere to write this run. Defaults to a new run directory.
--cache-dir DIRThe storage root. Defaults to $SENRO_CACHE_DIR.
--local-class CLASSMirrors senro.WithLocalClass.

Exit codes: 0 if the re-run succeeded, 1 if it failed, 2 for a usage error.

--step includes what the step needs

senro rerun --step deploy/apply-west

This runs deploy/apply-west, along with everything it needs (directly or transitively) and everything that depends on it. Any branch unrelated to it is skipped.

Including its dependencies is deliberate. A step can’t run without its inputs, so if you only selected the step and its dependents, the dependencies would be marked skipped, which would skip the dependents too. The one step you actually asked for would end up being the only thing that didn’t run. Including the dependencies costs little, since the unchanged ones are just cache hits.

The dependency graph comes from the recorded plan, so “everything below it” means exactly what it meant in the run you’re repeating.

--regenerate is a separate verb, deliberately

By default, a generator replays the subgraph it recorded, rather than being asked to generate again. This is what lets a re-run actually reproduce the original run: the generator might query an API that now answers differently, and replaying the recording guarantees you get the graph that actually ran the first time.

--regenerate asks for a fresh subgraph instead:

senro rerun --regenerate

Use --regenerate when things have genuinely changed and the recorded graph no longer describes reality, like a fleet that no longer exists. It’s a separate flag, not the default, because silently re-deriving the graph during what looks like a retry would be confusing: the run would quietly do different work than the one it claims to repeat.

A regenerated fragment gets recorded too, so the next plain senro rerun replays that new fragment.

Go generators cannot be regenerated from a recorded plan

A generator written with senro.Generate is a Go closure. It lives in your pipeline package and never makes it into plan.json, because a plan has to be serializable. So:

  • Without --regenerate, a Go generator replays from its cache entry, and everything works as normal.
  • With --regenerate, there’s no closure to call. The run fails and names the step, rather than quietly replaying it and pretending it regenerated.

A generator declared with GenerateFromJSON has no such limitation. Its fragment is just a file the step writes, so re-running the step produces a fresh one.

Where to go next

  • Generated subgraphs: what is being replayed, and why recording it is what lets a generator be nondeterministic.
  • cache & verify: why a step hit or missed, and re-checking a Pure() claim.