Repository navigation
Description
Problem
pm-stack/opencode-claimant/install.mjs — the installer that writes the #3026 claimant scheduled task into the laptop's real OpenChamber project store — performs no argument validation. main() (install.mjs:65-71) collects only the spellings it knows:
const args = new Set(argv.filter((a) => a.startsWith("--")));
const storeIdx = argv.indexOf("--store");
const storePath = storeIdx >= 0 ? argv[storeIdx + 1] : DEFAULT_STORE;
...
enabled: args.has("--enable") ? true : (existing?.enabled ?? false),Everything else on the command line is silently dropped, and the script proceeds to do a real install: it backs up and then atomically renames over the target store (install.mjs:91-94). Three concrete failure shapes:
- A typo'd
--dry-rnuperforms a real install. The operator believes they are previewing; the store is actually rewritten and the claimant task inserted/refreshed. If the task already exists enabled in that store, this also silently refreshes the live task's prompt to the currentprompt.md— a production behavior change made by a preview command. - Any unknown flag (
--bogus-flag, a stray positional) is ignored the same way, with exit 0 and a normal-looking summary. The only difference between the dry-run and real-run summaries is adry_run: truefield the operator has to notice the absence of. - A typo'd
--stor <path>falls back toDEFAULT_STORE(install.mjs:69-71:indexOf("--store")misses, so the real~/.config/openchamber/projects/…store is used) — a preview aimed at a scratch store writes the operator's real OpenChamber store instead. (Code-read consequence; not probed, for obvious reasons.)
Evidence (verified this run, main 3186e25)
install.mjs sha256 2ff84f5a48b2… and prompt.md sha256 dec788608534… are identical in the sandbox clone at 3186e25 and the laptop clone. Laptop probe ~/git/fleet/probe-2026-10-31-laneA-claimant-install/ (node v24.21.0, real install.mjs, temp stores seeded with one unrelated task):
node install.mjs --store store-typo.json --dry-rnu→ exit 0, summary{action: "inserted", tasks_before: 1, tasks_after: 2}with nodry_runmarker; the store afterwards contains the claimant taskf1b4a33b-14d5-4a69-9ad6-d221e42e3ac9alongside the seed task, and astore-typo.json.bak-<stamp>backup was created — i.e. a real write happened.- Control: the same command with correctly spelled
--dry-run→ store byte-identical afterwards (still only the seed task, 84 bytes), no backup file created. node install.mjs --store store-bogus.json --bogus-flag→ same real write as the typo case (task inserted, backup created, exit 0).
Existing tests (pm-stack/opencode-claimant/test/install.test.mjs, 7 tests) cover buildTask/mergeTasks only; nothing exercises main()'s argument handling, so no test pins the unknown-argument behavior either way.
Why it matters
This installer's whole safety story is in its header comment: the store "is runtime state", installing is an idempotent merge, and the task "installs DISABLED … enabling is the parallel-run gate's decision". A silent-argument-drop defeats the two operator-facing safeguards on that story — --dry-run (preview before touching runtime state) and --store (aim at a scratch store, not the real one). The failure is silent and exit-0, so scripts and operators cannot tell a preview from a write except by diffing the store afterwards.
Distinct from #3264 (same silent-args pattern in pkgs/pkarr-worker/deploy.mjs, a different tool whose consequence is a production deploy; that issue's fix does not touch this file) and from #3252/#3261 (pkarr-worker deploy/retention arguments).
Acceptance criteria
- Any unrecognized argument — unknown
--flag, unexpected positional, or a--storewith no following value — makesinstall.mjsexit non-zero with a usage error naming the offending argument, and writes nothing (no store write, no backup file). -
--dry-rnu,--bogus-flag, and--stor <path>are each refused by a regression test that drivesmain()(or an extracted argument parser) against a temp store and asserts the store is byte-identical afterwards. - The documented invocations keep working exactly as before: bare install,
--dry-run(no write, no backup),--store <path>,--enable, and their combinations; the existing 7install.test.mjstests still pass. -
--enablehandling is unchanged in meaning: a typo must never silently enable, and a refused invocation must never change an existing task'senabledstate.
Activity
dezren39 commentedon Nov 1, 2026
🤖 Agent note · filed
--dry-rnuand--bogus-flagagainst temp stores each performed a real write (claimant task inserted, .bak backup created, exit 0), while the correctly spelled--dry-runcontrol left its store byte-identical with no backup.--storfalls back to DEFAULT_STORE, the real OpenChamber project store.--store) exits non-zero with a usage error and writes nothing, regression tests drive main()/the parser for--dry-rnu/--bogus-flag/--stor, and the documented invocations plus the existing 7 install.test.mjs tests still pass.dezren39 commentedon Nov 1, 2026
Adopted as Phase 1 of design #3352 (incl. the --store fallback: an unrecognized value-option spelling must not silently re-aim the tool). The design records the contract this issue's fix must follow: every argument is accounted for before any side effect; unknown/typo'd arguments are a usage error naming the argument (exit non-zero, 2 where no usage code is established) before any subprocess/network/file/lock/deploy step. Triage: do NOT re-file, re-scope or split this issue — record the phase structure and verdict, and keep this issue's own acceptance criteria as the phase's acceptance. Design doc: files/designs/code-operator-argument-contract.md (keep-the-four-repos-healthy goal workspace).
dezren39 commentedon Nov 1, 2026
🤖 Agent note · picked up
repo-health-code-pilot; issue revalidated open on 2026-11-01.5 remaining items
dezren39 commentedon Nov 1, 2026
🤖 Agent note · follow-up queued
dezren39 commentedon Nov 2, 2026
🤖 Agent note · picked up
dezren39 commentedon Nov 2, 2026
🤖 Agent note · done (PR opened)
bun test— 12 pass, 0 fail, 88 assertions;git diff --checkpasses. PR checks are queued; this lane did not wait on CI.pr_opened; issue opencode-claimant(install): unrecognized arguments are silently ignored — a typo'd --dry-rnu performs a real install, and a typo'd --stor writes the real OpenChamber store #3278 remains OPEN. Queue/status/work labels set by this claim have been removed.