What to Do When PACE Issues a HOLD
A HOLD is a hard block. The day does not advance, no PR is opened, and the orchestrator exits with a non-zero status. Three review agents can issue a HOLD: GATE, SENTINEL, and CONDUIT. Each HOLD includes a hold_reason — a human-readable description of what failed and, where possible, what needs to change.
Step 1 — Find the hold_reason
The hold_reason appears in three places.
Terminal output — printed before the orchestrator exits:
[PACE] GATE: HOLD[PACE] hold_reason: 3 of 5 acceptance criteria failed. Criterion 3 ("POST /users returns 201") failed — test UserCreationTest::test_create_user not found in test output. FORGE must add the missing test before this story can ship.Escalation issue — if platform.ci is set to github, gitlab, or another platform, PACE opens an issue automatically with the hold_reason as the issue body.
Artifact files — in .pace/day-N/:
cat .pace/day-1/gate-report.yamlcat .pace/day-1/sentinel-report.yamlcat .pace/day-1/conduit-report.yamlEach report has a decision field set to either SHIP, HOLD, or PARTIAL. Read the report from the agent that issued the HOLD.
Step 2 — Identify which agent issued the HOLD
grep -l "HOLD" .pace/day-1/*.yaml| Agent | Common HOLD causes |
|---|---|
| GATE | Tests failing, acceptance criterion not met, test not found in output, coverage below threshold |
| SENTINEL | Exploitable vulnerability (hardcoded secret, SQL injection, missing auth), FAIL-level finding |
| CONDUIT | Broken CI workflow, action pinned to @latest, failing step that does not block merge |
Step 3 — Diagnose the issue
If GATE issued the HOLD
Run your test command manually from the repo root and read the full output:
# Pythonpytest -v --tb=short
# Gogo test ./... -v
# Node.jsnpm test -- --forceExit
# Javamvn -q test
# C#dotnet test -v minimalMatch the failure message to one of these common GATE HOLD patterns:
| GATE HOLD pattern | What it means | What to do |
|---|---|---|
test not found in output | FORGE wrote the feature code but did not write the test | Add the missing test to your test suite |
criterion N failed — no evidence | Feature was built but the test does not assert the criterion | Review and strengthen the test assertion |
exit code 1 | A test assertion failed | Read the failure message; fix code or fix the assertion |
coverage below threshold | Not enough test coverage | Add tests until coverage meets the configured threshold |
If SENTINEL issued the HOLD
Read the sentinel report and find the FAIL-level finding:
cat .pace/day-1/sentinel-report.yamlLook for verdict: FAIL entries. Sentinel HOLDs are for exploitable vulnerabilities only — not advisories. Common cases:
| Finding type | Example | Fix |
|---|---|---|
| Hardcoded secret | API key committed to source | Move to environment variable; rotate the key |
| SQL injection | String concatenation in query | Use parameterised queries |
| Missing authentication | Sensitive endpoint has no auth check | Add auth middleware or guard |
| Unsafe deserialization | Deserializing untrusted input without validation | Use a safe deserialization library or validate input first |
If CONDUIT issued the HOLD
Read the conduit report:
cat .pace/day-1/conduit-report.yamlConduit HOLDs are rare and indicate a CI/CD configuration problem that would break your pipeline. Common cases:
| Finding | Fix |
|---|---|
Action pinned to @master or @latest | Pin to a specific commit SHA or version tag (e.g., actions/checkout@v4) |
| Test step that does not block merge | Update branch protection rules to require the step |
| Makefile target referenced in CI does not exist | Add the missing target or fix the reference in the workflow file |
Step 4 — Decide: fix code, adjust criteria, or mark out_of_scope
You have three options after reading the hold_reason. Choose the one that matches the situation.
Option A — Fix the code
The right choice when the hold_reason is clear and the fix is straightforward. Make the change, run the test command locally to confirm it passes, then re-run the day.
Option B — Adjust the acceptance criterion
The right choice when the criterion was written too strictly and the actual outcome is acceptable:
# Before (too strict — brittlely tests the exact response shape):acceptance_criteria: - "POST /users returns 201 with user JSON including all fields"
# After (accepts the core behaviour):acceptance_criteria: - "POST /users returns 201 with user JSON"Edit plan.yaml, save it, and re-run the day. FORGE will re-evaluate against the updated criteria.
Option C — Add to out_of_scope
The right choice when the acceptance criterion describes work that is genuinely deferred to a later story:
stories: - id: story-1 title: "User registration endpoint" status: pending acceptance_criteria: - "POST /users returns 201 with user JSON" - "pytest -q exits 0" out_of_scope: - "Email verification — deferred to story-3"GATE marks criteria that map to an out_of_scope entry as PARTIAL rather than FAIL. A story with only SHIP and PARTIAL verdicts (no FAIL) can ship.
Step 5 — Re-run the day
After making your fix:
python pace/orchestrator.py --day 1PACE runs the full pipeline again from PRIME. The previous attempt’s cost is recorded so you can see the total:
cat .pace/day-1/attempts.yamlIf the HOLD persists, read the new hold_reason carefully — it may differ from the first one. FORGE may have fixed the original issue but introduced a new failure.
Step 6 — Escalate if retries are exhausted
If the pipeline has HOLDed twice and the remaining issue appears to be in the acceptance criteria or story scope (not in the code):
- Review
plan.yaml— adjust acceptance criteria or addout_of_scopeentries as needed - Re-run the day
If you believe the issue is a PACE bug:
- Collect the full artifact set:
ls .pace/day-N/ - Review
attempts.yamlfor the full attempt history - Open an issue on the PACE repository with the relevant
hold_reasonand the artifact content
Quick reference — HOLD decision tree
HOLD received │ ├── GATE HOLD? │ ├── Tests failing → fix code or tests → re-run │ ├── Test not found → add the missing test → re-run │ └── Criterion not met → adjust criterion or add out_of_scope → re-run │ ├── SENTINEL HOLD? │ └── Security vulnerability → fix the code → re-run │ (cannot be bypassed with out_of_scope) │ └── CONDUIT HOLD? └── CI/CD issue → fix the workflow → re-run (cannot be bypassed with out_of_scope)