Summary
NERVE_DRAFT_SPAWN_CMD is not set inside the container, which
half-wires the watcher cron's draft-orchestrator path. The
orchestrator runs the conflict classifier, decides a draft sub-
session is required, then aborts with:
spawn failed: NERVE_DRAFT_SPAWN_CMD is unset; sub-session spawner not configured
The fallback emits a [BLOCKED] meta.dismiss-only notification,
which is correct. The bug is that the orchestrator ALSO files a
separate github.resolve-conflict TAP card with a naive git push
exec command pointing at the temp worktree under
~/.nerve/conflict-resolutions/<id>/worktree/. The temp worktree
never had the resolution committed, so when the operator taps
Approve, the push exits Everything up-to-date and the queue ends
up with two contradictory cards for the same target.
Two defects in one report
-
Spawner env var is not exposed to the container. Either
the entrypoint should set NERVE_DRAFT_SPAWN_CMD so the
orchestrator can spawn a sub-session that runs the
compose-conflict-resolution skill, or the orchestrator should
short-circuit (skip the draft path entirely) when the variable
is unset.
-
Orchestrator files an unsafe github.resolve-conflict card
when its spawn fails. A TAP card whose exec_command is a
bare git push against an un-resolved temp worktree is
misleading. Two valid fixes: stop filing the resolve-conflict
card when the spawn aborts (let the meta.dismiss-only card
stand alone), or only file it after the validator has confirmed
a real draft SHA exists.
The first defect is the root cause; the second is what makes the
recovery silently fail when the operator acts on what looks like a
normal queue entry.
Repro signature
When the queue contains both:
- a
github.resolve-conflict whose exec_command is a bare
git push with no preceding commit step, AND
- a
meta.dismiss-only for the same target filed within the same
minute,
the spawner-abort path fired. The meta.dismiss-only is the truth;
decline or ignore the resolve-conflict card and resolve the
conflict manually via the compose-conflict-resolution skill in
the canonical worktree.
Audit log rows for the failure live in
~/.nerve/mechanical-actions/audit.jsonl. Classifier output is
cached under ~/.nerve/conflict-resolutions/<draft-id>/classifier.json.
Proposal payloads sit in ~/.nerve/mechanical-actions/decisions/.
Suggested fix order
Land the orchestrator change first (defect 2). Smaller diff, stops
the queue from producing contradictory cards. Then wire the
spawner (defect 1) so the autonomous path actually works end-to-end.
Summary
NERVE_DRAFT_SPAWN_CMDis not set inside the container, whichhalf-wires the watcher cron's draft-orchestrator path. The
orchestrator runs the conflict classifier, decides a draft sub-
session is required, then aborts with:
The fallback emits a
[BLOCKED]meta.dismiss-onlynotification,which is correct. The bug is that the orchestrator ALSO files a
separate
github.resolve-conflictTAP card with a naivegit pushexec command pointing at the temp worktree under
~/.nerve/conflict-resolutions/<id>/worktree/. The temp worktreenever had the resolution committed, so when the operator taps
Approve, the push exits
Everything up-to-dateand the queue endsup with two contradictory cards for the same target.
Two defects in one report
Spawner env var is not exposed to the container. Either
the entrypoint should set
NERVE_DRAFT_SPAWN_CMDso theorchestrator can spawn a sub-session that runs the
compose-conflict-resolutionskill, or the orchestrator shouldshort-circuit (skip the draft path entirely) when the variable
is unset.
Orchestrator files an unsafe
github.resolve-conflictcardwhen its spawn fails. A TAP card whose
exec_commandis abare
git pushagainst an un-resolved temp worktree ismisleading. Two valid fixes: stop filing the resolve-conflict
card when the spawn aborts (let the
meta.dismiss-onlycardstand alone), or only file it after the validator has confirmed
a real draft SHA exists.
The first defect is the root cause; the second is what makes the
recovery silently fail when the operator acts on what looks like a
normal queue entry.
Repro signature
When the queue contains both:
github.resolve-conflictwhoseexec_commandis a baregit pushwith no preceding commit step, ANDmeta.dismiss-onlyfor the same target filed within the sameminute,
the spawner-abort path fired. The
meta.dismiss-onlyis the truth;decline or ignore the resolve-conflict card and resolve the
conflict manually via the
compose-conflict-resolutionskill inthe canonical worktree.
Audit log rows for the failure live in
~/.nerve/mechanical-actions/audit.jsonl. Classifier output iscached under
~/.nerve/conflict-resolutions/<draft-id>/classifier.json.Proposal payloads sit in
~/.nerve/mechanical-actions/decisions/.Suggested fix order
Land the orchestrator change first (defect 2). Smaller diff, stops
the queue from producing contradictory cards. Then wire the
spawner (defect 1) so the autonomous path actually works end-to-end.