Completion Gate (stop_completion)
Configures the completion gate — the stop_completion op that runs on Stop and enforces Navigator’s finish protocol from observable evidence, not model self-reports. When a codebase-mutating turn ends with unfinished work and no explicit exit signal, the gate forces one continuation so the work actually finishes (commit, docs, ticket, marker) instead of trailing off.
New in v7.0.0. Forced continuation ships off (new blocking features seed off); you opt in explicitly.
Config block
In .agent/.nav-config.json:
{
"stop_completion": {
"enabled": false,
"continue_enabled": false,
"max_continues": 2
}
}Keys
enabled(defaultfalse) — Evaluate completion indicators on Stop. Both this andcontinue_enabledmust betruefor the gate to act; a missing block is off.continue_enabled(defaultfalse) — Allow the forced continuation. This is the blocking behavior and the gate’s primary off-switch.max_continues(default2) — Hard cap on forced continuations, tracked in runtime state and reset at the next clean turn end.
The 6 completion indicators
Indicators are derived from observable turn evidence — the git tree, test runs, docs/marker paths, ticket state, and the simplification pass — using the same exit_gate.evaluate_exit vocabulary Loop Mode uses:
| Indicator | Evidence |
|---|---|
code_committed | git status --porcelain shows no tracked change. Untracked ?? paths are skipped unless the turn itself touched them, so a repo that keeps scratch files untracked on purpose can still meet it (v8.3.2). |
tests_passing | A test command (make test, make mod-test, pytest, unittest) ran this turn and did not error. |
code_simplified | The simplification pass ran (when enabled). |
docs_updated | A *.md file was touched: an Edit/Write path, or a mutating Bash command that names one (sed -i … README.md, v8.3.2). |
ticket_closed | The PM ticket was closed (when PM integration is configured). |
marker_created | A completion/context marker was created. |
Forced-continuation semantics
The gate forces a continuation only when both conditions hold:
- Completion indicators are unmet, and
- No explicit exit signal is present in the final assistant message.
When it fires, exactly one continuation is injected per turn (the reason appears as Stop hook feedback), and the total is capped by max_continues. If you genuinely want to stop mid-task, emit the exit signal — the gate never overrides an explicit exit.
Circuit breakers
The gate carries a layered breaker set so it cannot loop or fire on the wrong turns:
- Single-shot fuse — at most one forced continuation per turn; re-armed only at the next clean turn end.
max_continuescap — held count is capped (default2) and reset on clean turn end.- Mutating-turn requirement — never fires on conversational or read-only turns. Bash-only turns are classified by a read-only command allowlist and, above that, by working-tree digest evidence: an unchanged
git statusdigest across consecutive Stops overrules the classification, so ops work never reads as “mutated the codebase”. The allowlist knowscdand resolves absolute-path heads such as/bin/lsto their basename (v8.2.6); unknown heads still count as mutating, so the gate may over-fire but never under-fire. A turn whose only action is a read-only subagent (Explore, Plan, claude-code-guide, navigator-research, task-planner) is not mutating either; unknown agent types still are (v8.2.8). Quoted spans are masked before the command is split, so a|or>inside a grep pattern or a--jqfilter never makes a turn mutating (v8.3.1). A redirect target ends at the operator (2>/dev/null;is/dev/null), a subshell, group or function body is classified by what runs inside ((cd x && git status)reads,(rm x)writes), andgh run watch,claude plugin list|validate|test|update,python3 -m json.tool,awkwithout>andmakewith only test-shaped targets (test|check|typecheck|validate) are read-only, so a turn that only runs the suite is not a task action (v8.3.3). - Stop-hook re-entry guard — short-circuits when the harness reports an active stop hook.
- Kill switches —
enabledandcontinue_enabledmust both be truthy;PILOT_EXECUTORdisables the gate unconditionally, regardless of config.
Enabling
{
"stop_completion": {
"enabled": true,
"continue_enabled": true,
"max_continues": 2
}
}Start with the default cap of 2; raise it only if you routinely see legitimate multi-step finishes being cut short.
When it fires wrongly
Every block the gate makes is one line in .agent/.nav-rejects.jsonl (v8.2.0,
Reject Log) with evidence.mutating_tools, the indicators met and
unmet. A gate that fires on a read-only turn shows up as "mutating_tools":["Bash"] in one
grep — the diagnosis that took a transcript read before.
When NOT to enable
Do not enable the gate under an autonomous executor (Pilot or any external loop supervisor). Two loop supervisors fighting over continuation produces livelock, not throughput. Navigator enforces this: when the PILOT_EXECUTOR environment variable is set, the gate is off unconditionally — but if you run a different executor, leave continue_enabled: false yourself. Also skip it in exploratory/research sessions where turns intentionally end without commits.