A gate block on a step lets you define a success condition and a retry strategy. When the condition fails, Shipfox restarts execution from an earlier keyed step in the same job — creating a loop that continues until the condition is met or the job times out. Pair it with an agent step and the loop does real work: the agent edits code, a shell step verifies it, and the gate loops back — no scripting.
Gate schema
A gate block sits directly on a step and supports two fields:
gate:
success_if: exit_code == 0 # CEL expression evaluated against the step outcome
on_failure:
restart_from: install # must reference an earlier step that has a `key:` field
success_if and on_failure are both optional individually, but at least one must be present. A gate with only success_if marks the step as failed without retrying. A gate with only on_failure always retries. Use both together for a conditional retry loop.
Basic retry loop
The canonical pattern installs dependencies, runs an agent to fix failing tests, then verifies:
jobs:
fix:
steps:
- key: install
run: npm ci
- model: claude-opus-4-8
prompt: Fix the failing tests in the repository.
- run: npm test
gate:
success_if: exit_code == 0
on_failure:
restart_from: install
On each iteration, the agent reads the updated codebase and attempts another fix. The loop continues until npm test exits 0. The loop is bounded automatically: a gating step gets 3 attempts by default before its restart budget is exhausted and the job stops. The job-level execution_timeout adds a second, outer bound — see Loop bounds.
success_if expressions
The success_if field accepts a CEL (Common Expression Language) expression that is evaluated after the step completes. The only variable available today is exit_code, which holds the integer exit code of the step’s shell command.
Common patterns:
| Expression | Meaning |
|---|
exit_code == 0 | Step succeeded (standard Unix convention) |
exit_code != 1 | Any exit code except a hard failure |
More CEL variables (stdout content, step duration, etc.) are planned for future releases.
Rules for restart_from
The step referenced by restart_from must appear before the gate step in the same job. Forward references are not allowed.
- The target step must have a
key: field — restart_from matches keys, not display names. (name: is a separate, display-only label.)
- The reference is matched by step key, not by position — reordering steps changes which step is targeted.
restart_from must reference a step within the same job. Cross-job retries are not supported; use needs to express job dependencies instead.
Job execution timeout
A gating step already stops after a bounded number of restarts (3 by default). On top of that, execution_timeout sets an outer wall-clock bound on the whole job — useful when steps are slow and even a few attempts could run long:
jobs:
fix:
execution_timeout: "30m"
steps:
- key: install
run: npm ci
- model: claude-opus-4-8
prompt: Fix the failing tests in the repository.
- run: npm test
gate:
success_if: exit_code == 0
on_failure:
restart_from: install
The value is a duration string. When the timeout is reached, the job is cancelled and marked as failed regardless of where execution is in the loop.
Next steps
- Jobs & Steps — understand job isolation,
needs, and parallel execution.
- Agent Steps — configure the model, thinking level, and provider for agent steps.