Skip to content

fix: three filed bugs where codexclaw obstructed its own instructions (#47, #48, #49) - #51

Merged
lidge-jun merged 5 commits into
mainfrom
dev
Aug 22, 2026
Merged

fix: three filed bugs where codexclaw obstructed its own instructions (#47, #48, #49)#51
lidge-jun merged 5 commits into
mainfrom
dev

Conversation

@lidge-jun

Copy link
Copy Markdown
Owner

Promotes dev to main for 0.2.12: three filed bugs, all of them cases where
codexclaw obstructed an agent that was following its own instructions.

#47 — the sibling commands had no --help

cxc --help points at loop, scan and receipt. Following that pointer
failed: --help was an unknown verb on all three, and cxc --version was an
unknown command. orchestrate was fixed for exactly this in
260709_cxc_help_agent_ux; its siblings never were.

The individual error messages were fine. The defect is that discovery was only
available through failure — arming a goalplan in this session took six
consecutive rejections to assemble one command:

loop steer: --session <id> is required
loop steer: --batch-json <path-or-json> is required
loop steer: idempotencyKey is required and must be a non-empty string
loop steer: rationale is required and must be a non-empty string
loop steer: evidence is required and must be a non-empty string
loop steer: ops must be a non-empty array

The loop usage now spells out the steer batch shape. scan record also accepts
--cwd, which orchestrate already documented.

Worth noting: cxc --version worked from one entry point and still failed from
the other, because there are two. The test asserts both — that is the mistake it
exists to catch.

#48 — one session id, two FSMs

Session files live at <cwd>/.codexclaw/sessions/<id>.json. The id is stable,
the cwd is not, so a thread whose cwd is one tree while its work is in another
has two FSMs under one id. The reporter closed D in the wiki tree and the next
turn re-injected Interview from the other copy.

Pinning the store to the session-start workspace would invalidate every existing
session file with no migration path, so this makes the split visible instead:

session=<id> phase=IDLE ...
WARNING: this session id also has state in 1 other tree(s); the phase above
describes THIS cwd only.
  also at: .../cxc-split-a/.codexclaw/sessions/<id>.json

Detection only — the other tree is never read from or written to. loop show
also accepts --session and resolves the slug the session already carries.

#49 — two gates that forced the forgery they prevent

receipt test refused a receipt whenever the check dirtied the tree, including
when the dirty files were the artifacts the check exists to rebuild. The reported
workaround was to commit the generated files and run a no-op existence check — a
receipt that certifies nothing. A forged receipt is strictly worse than a loose
one
, because it satisfies CHECK-BINDING-01 while proving less than no receipt.

--generated <path> declares expected rewrites. Everything undeclared is still
refused, verified four ways including that a declared FILE does not cover its
siblings.

orchestrate D refused a goalplan whose work-phases were all done, because
"complete" and "empty" produced the same internal result. The workaround was to
write a finished phase back to in_progress purely to pass the gate — corrupting
the record to satisfy a check about the record.

SOURCE-DELTA-01 was raised in the same issue and deliberately left alone: I hit
it twice while doing this work and both times it was right.

Verification

  • native Windows: 1961 tests, 1953 pass, 0 fail, 8 skip
  • WSL Ubuntu: 1961 tests, 1960 pass, 0 fail, 1 skip
  • --generated verified end to end on a throwaway git repo whose validator
    rewrites its own output: refused without the flag, receipt written with it,
    still refused when the wrong path is declared
  • the #48 warning verified against two real trees holding the same id

…eady had (#47)

`cxc --help` points at the sibling commands, and following that pointer failed:
--help was reported as an unknown verb on loop, scan and receipt, and
`cxc --version` was an unknown command. orchestrate was fixed for exactly this
in 260709_cxc_help_agent_ux; its siblings never were.

The cost is not the error messages, which are individually fine. It is that
discovery was only available through failure. Arming a goalplan in this session
took six consecutive rejections to assemble one correct command:

  loop steer: --session <id> is required
  loop steer: --batch-json <path-or-json> is required
  loop steer: idempotencyKey is required and must be a non-empty string
  loop steer: rationale is required and must be a non-empty string
  loop steer: evidence is required and must be a non-empty string
  loop steer: ops must be a non-empty array

help | --help | -h now print usage and exit 0 on all three, and the loop usage
spells out the steer batch shape since that is the one nobody can guess.
Unknown verbs still fail, but now name the way out.

cxc --version reads the installed manifest; previously the only way to know
which payload was live was to read the cache directory name.

Also from the same issue: scan record now accepts --cwd, which orchestrate
already documented. The reporter's answer ledger was in one tree and the process
cwd in another, so --derive matched nothing and said so only as a warning.

help-verbs.test.ts asserts the contract (exit 0 plus a Usage: block) rather than
the wording, and pins the flags that were previously rejection-only.
… a side (#48)

Session files live at <cwd>/.codexclaw/sessions/<id>.json. The id is stable, the
cwd is not, so a thread whose process cwd is one tree while its work is in
another has two FSMs under one id and nothing says so. The reporter closed D in
the wiki tree and the next turn re-injected Interview from the other copy.

Pinning the store to the session-start workspace would invalidate every existing
session file with no migration path, so this makes the split visible rather than
flipping a coin. findForeignSessionCopies looks for the same id in plausible
sibling roots and orchestrate status reports what it finds:

  session=<id> phase=IDLE ...
  WARNING: this session id also has state in 1 other tree(s); the phase above
  describes THIS cwd only.
    also at: .../cxc-split-a/.codexclaw/sessions/<id>.json

Detection only - the other tree is never read from or written to. The candidate
list is deliberately shallow because this is a warning on a read-only command,
not a filesystem crawl.

Also from the same issue: loop show --slug was cwd-only and printed "no plan
found" from the wrong tree. loop init --session already binds the slug into the
session file, so resolveSlug now falls back to that binding - which also makes
the session the source of truth rather than whichever directory the shell was in.

The underlying cwd-keyed storage is unchanged; you are now told about the split
rather than misled by it.
… prevent (#49)

receipt test refused a receipt when the check command dirtied the tree, even when
the dirty files were the artifacts the check exists to rebuild. The ontology
validator IS the documented gate for that repo. The reported workaround was to
commit the generated files and run a no-op existence check instead - a receipt
that certifies nothing. A forged receipt is strictly worse than a loose one,
because it satisfies CHECK-BINDING-01 while proving less than no receipt at all.

--generated declares paths the check rewrites by design. Repeatable, repo-
relative, prefix-matched. Everything undeclared is still refused, verified four
ways: undeclared rewrite refused, wrong path declared still refused, a declared
FILE does not cover its siblings, and a missing value is a parse error rather
than a silent skip. The refusal now names the flag so the next agent finds the
sanctioned route instead of reinventing the no-op trick, and the receipt records
generatedPaths so a reader sees what was permitted.

orchestrate D refused a goalplan whose work-phases were all done, because
advanceWorkPhase returns no_active for both 'plan is empty' and 'plan is
complete'. The reported workaround was to write a finished phase back to
in_progress purely to pass the gate - corrupting the record to satisfy a check
about the record. D now closes over a complete plan, and the refusal names which
real cause applies (empty, or everything blocked). The goalplan ledger says
'cycle closed over an already-complete plan' rather than 'closed null'.

SOURCE-DELTA-01 was also raised. Left unchanged deliberately: I hit it twice in
this session and both times it was right. A gate that occasionally annoys beats
one that lets an empty B through.
@lidge-jun
lidge-jun merged commit 6764340 into main Aug 22, 2026
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant