Skip to content

NERVE_DRAFT_SPAWN_CMD env var unset in container #91

@alex-fedotyev

Description

@alex-fedotyev

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

  1. 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.

  2. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions