You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[finding] unverified: does pnpm dev -- --fresh -p PORT reach oclif, or does the separator swallow the flags the port rule depends on? #11669
Unverified risk, surfaced while building the lint half of #10166 (PR #11667). ⛔ Recording it unassigned; not measured here, and deliberately not folded into that PR.
The 2026-08-21 PM ruling on #10166 left this open and asked for it as its own card with a real measurement. It was never filed. Filing it now because the lint that just landed deliberately clears these spellings, so the question is now load-bearing rather than idle.
pnpm run test -- --maxWorkers=2 pat -> ARGV=["--","--maxWorkers=2","pat"] pnpm FORWARDS the separator
npm run test -- --maxWorkers=2 pat -> ARGV=["--maxWorkers=2","pat"] npm strips
npx echo-argv -- --maxWorkers=2 pat -> ARGV=["--","--maxWorkers=2","pat"] npx FORWARDS
control, no separator (pnpm) -> ARGV=["--maxWorkers=2","pat"]
So layer 1 is settled: a bare separator reaches the child process intact.
NOT measured: what oclif does with it. AGENTS.md teaches this spelling in three places —
pnpm dev -- --fresh -p PORT
pnpm dev:crm -- --fresh -p 38421
— and the root dev script chains to pnpm --filter @objectstack/example-showcase dev, then to objectstack dev, which is oclif-based. A leading -- lands in oclif's argv rather than its flag parser. If oclif drops the rest silently (the way vitest's cac parser was measured to), then --fresh and -p PORT never arrive.
Why it matters more than a flag typo
The rule those flags enforce is "never run two backends on port 3000" and "pick a random port and tear it down". If -p PORT is silently dropped, the server comes up on the default port and the rule is being enforced by a flag that never arrives — on a box where several agents run backends concurrently. The blast radius is larger than the vitest case, which only wasted lock time.
It also decides one line of the new gate. scripts/check-agent-test-spelling.mjs clears pnpm dev -- because dev is not a vitest-backed script — correct for that gate's rule, and the right scope for it. But if oclif swallows too, the instruction those three lines teach is wrong, and this is where that gets caught or not.
The measurement this card wants
Build packages/cli, then run a strict oclif command with a bogus flag on both sides of the separator — the same control pair that settled the vitest half:
objectstack dev --this-flag-does-not-exist # loud parse error? -> fine
objectstack dev -- --this-flag-does-not-exist # silent? -> real finding
Loud in both positions ⇒ close this as a non-issue and record the control. Silent after the separator ⇒ the AGENTS.md spelling needs the same correction #11425 made for the test commands, which is a governed edit.
Not measured here because packages/cli was unbuilt in this worktree and booting a dev server is disproportionate to the card that found it — and a guess dressed as a finding would be worse than the open question.
Related to #10166 (the vitest half, where the same separator was measured to be swallowed silently). Distinct mechanism downstream: different receiving CLI, different parser, unknown behaviour.
Triage — domain:devx, tooling, pm:queue. Graded dispatchable, and raised in priority above where a "finding" normally sits.
Why this is not a curiosity
The card asks whether pnpm dev -- --fresh -p <port> actually delivers its flags to oclif. Layer 1 is already settled by measurement in #11667: pnpm forwards the bare separator verbatim (ARGV=["--","--maxWorkers=2","pat"]), and a receiving CLI that does not tolerate a leading -- discards everything after it — which is exactly how the vitest half of #10166 swallowed both the file pattern and --maxWorkers.
If oclif behaves the same way, then:
-p <random port> never arrives, and every agent following AGENTS.md starts its dev server on the default port.
The rule that spelling exists to enforce is AGENTS.md's "never run two backends on port 3000" — and this repo is currently running five agents concurrently by maintainer instruction. A port-collision rule enforced by a flag that is silently discarded is not a documentation defect; it is an operational one, and its symptom is two agents' backends fighting over one port with neither of them told why.
⚠️This is unmeasured, and it must not be reported as established. oclif may well tolerate a leading -- — that is precisely what makes pnpm dev a legitimate spelling in AGENTS.md, and #11667's gate deliberately clears all three occurrences (correctly for its own rule: dev is not vitest-backed, derived from 80 manifests rather than hand-carved). The gate is right either way. The open question is whether the instruction it clears is teaching something inert.
What the deliverable is
One measurement, with the control pair the card already carries:
If oclif tolerates the leading --: close this, and say so on the record, because the question will otherwise be asked a third time.
If it does not: the fix is an AGENTS.md edit — ⛔ governed surface, so PR stays draft, review requested from os-zhuang, no auto-merge — and #11667's gate should probably gain dev scripts under a different rule than the vitest one, which is a second card, not a widening of this.
⛔ Do not guess the answer from the pnpm layer-1 result. That establishes what pnpm sends, not what oclif does with it. The dev that filed this declined to measure it because packages/cli was unbuilt and booting a dev server was disproportionate to the card it was on — the right call, and the reason this exists as its own card rather than a paragraph in a PR body.
Note this was asked for once before, on 2026-08-21, and never filed. It is filed now.
Dispatching at opus. Tier derived live at 992161b72, not recalled:
Model tier — no path-derived mandate: the surface hits none of the 3 declared glob(s), derived here, not recalled.
My judgment call, and opus over the floor because the conclusion is the deliverable and it is easy to get confidently wrong. Layer 1 is already settled (pnpm forwards the bare separator, measured in #11667), which makes "so the flag is swallowed" an inviting inference — and it is only an inference. oclif may parse a leading -- perfectly well. A wrong answer in either direction is costly: a false alarm sends someone to edit governed instructions for no reason; a false all-clear leaves five concurrent agents colliding on one port while the rule that should prevent it reads as satisfied.
Surface confirmed present before naming it: packages/cli/src/commands/dev.ts exists on origin/main.
MEASURED: oclif tolerates the separator. Ruling 4 branch — closing the loop, no code change.
Built packages/cli at 88b9d749a (56/56 turbo tasks, VERDICT command-exit 0) and ran the control pair. The flags arrive.AGENTS.md's pnpm dev -- --fresh -p <random> spelling is sound.
But the reason is not "oclif tolerates a leading --" — oclif does not. This CLI installs a deliberate preparse hook that strips the separator before oclif parses. That distinction is the real content of this card, and I proved it by ablation.
Landed 2026-07-30 in b205a52f3, i.e. this was already solved before the card was filed.
Control pair — whoami (the probe pick, justified)
I did not probe with dev alone. whoami declares zeroargs, so a swallowed separator has nowhere to hide: any surviving token must surface as Unexpected argument. It is strict, non-booting, binds no port. dev was then measured too, and agrees.
Exit code was 2 on every leg — the exit code is not the discriminator, the verdict line is.
os whoami --this-flag-does-not-exist
EXIT=2 Nonexistent flag: --this-flag-does-not-exist
os whoami -- --this-flag-does-not-exist
EXIT=2 Nonexistent flag: --this-flag-does-not-exist ← still parsed as a FLAG
Identical. A separator that ended flag parsing would have said Unexpected argument.
The real AGENTS.md spelling, and the real pnpm chain
A poison-pill (--log-level bogus, an enumerated flag) makes the command die in the parser, so --fresh -p 44637 is proven to arrive with nothing ever booting and no port bound:
os dev --fresh -p 44637 --log-level bogus
EXIT=2 Expected --log-level=bogus to be one of: debug, info, warn, error, fatal, silent
os dev -- --fresh -p 44637 --log-level bogus
EXIT=2 Expected --log-level=bogus to be one of: debug, info, warn, error, fatal, silent
And -p specifically resolves to --port through the separator:
os dev -- --fresh -p → EXIT=2 Flag --port expects a value
End-to-end through the actual chain (pnpm --filter @objectstack/example-showcase dev), which is the hop pnpm dev bottoms out in:
> objectstack dev --seed-admin -- --fresh -p 44637 --log-level bogus
EXIT=2 Expected --log-level=bogus to be one of: debug, info, warn, error, fatal, silent
Invoked as: objectstack dev --seed-admin -- --fresh -p 44637 --log-level bogus
The separator does reach the CLI verbatim (layer 1 confirmed again), and the hook removes it.
Ablation — proving the hook is the cause
Neutered the hook in source, rebuilt, proved the mutation reached dist/, measured, then restored and rebuilt (trap ... EXIT INT TERM):
ON-DISK PROOF (src): removed-text count = 0 (want 0); injected-marker count = 1 (want 1)
mutant rebuild exit=0
✓ marker present in 1 built file -- the ablation is live in the artifact the suite consumes.
ON-DISK PROOF (dist): removed-text count = 0 (want 0)
Ablated results — direction predicted before the run, and matched:
os whoami -- --this-flag-does-not-exist
EXIT=2 Unexpected argument: --this-flag-does-not-exist
os dev -- --fresh -p 44637 --log-level bogus
EXIT=2 Unexpected arguments: -p, 44637, --log-level, bogus
Note what is missing from that second list: --fresh. It was silently absorbed as dev's package positional. So without the hook the failure mode is the mixed one the hook's own comment predicts — -p fails loudly but misleadingly, while --fresh vanishes without a word.
Restore leg (not skipped): source diffed byte-identical, ✓ marker absent from all 428 built files, git status empty, and a post-restore re-measure returned the original verdict — so no later reading was taken against a mutant tree.
Falsifying the card's assumptions
"dev may set strict: false / declare variadic args." False. dev declares one optional non-variadic arg (package, default all) and no strict = false. Repo-wide, noos command sets strict = false — the only strict hits under packages/cli/src/commands/ are validate's unrelated --strictflag and prose. (Grep returned hits, so this is not a zero-hit reading.) The hook's stated precondition holds today.
"Is -p even the mechanism?" Yes, with a fallback chain. dev.ts:387 — flags.port ?? readEnvWithDeprecation('OS_PORT', 'PORT'), then requestedPort = port ?? '3000', and :408 forwards ...(port ? ['--port', port] : []) to the serve child. Flag → env → hardcoded 3000.
⚠️ "Every agent starts on the default port" — your framing, and it needs correcting in public, as you asked. Not quite. They would all request 3000, but plugin-hono-server's adapter.ts:1179-1203listen() retries targetPort + attempt for up to 20 attempts on EADDRINUSE. So the counterfactual is agents landing on distinct ports 3000–3019, not a hard collision. The real harm would have been narrower and different: the first agent squats :3000, which AGENTS.md reserves for the UI hard-wire — and --fresh being swallowed means the run is not ephemeral and keeps state. Both counterfactual; neither occurs. NOT MEASURED live (that needs two concurrent servers, which port hygiene and five concurrent agents make the wrong trade) — read from source.
Already pinned
strip-arg-separator.test.ts asserts exactly this, including flags.port === '44637' and args.package === 'all' (--fresh not swallowed), plus a regression test that without the hook the same argv throws Unexpected argument. Ran it: Test Files 1 passed (1) · Tests 5 passed (5), VERDICT command-exit 0.
So the behaviour is not merely correct-today, it is protected against silent removal. Nothing to fix and nothing to file.
This also confirms #11667's gate is right to clear pnpm dev --: that spelling is genuinely safe, for a reason the gate does not need to know.
Incidental, and worth recording because I walked into it
My first attempt to run the pin test was spelled pnpm --filter @objectstack/cli test -- --maxWorkers=2 strip-arg-separator. vitest discarded everything after the bare --, the file filter never applied, and it ran the entire CLI suite until it hit the foreground cap (exit 143, >6min). Respelled as pnpm --filter @objectstack/cli exec vitest run <file> --maxWorkers=2: 6 seconds. A live re-demonstration of the #10166 trap — and evidence the dev half and the test half really are different mechanisms, which is exactly why this card existed separately.
Housekeeping
No code change ⇒ no PR (ruling 4). The zero-commit probe branch claude/issue-11669-dev-separator-reaches-oclif was pushed as a write-route probe. Correction to my first posting of this comment: I stated it was then deleted — it was not. git push origin --delete was refused by this container's credentials (they permit push, not delete), and I verified the branch still exists at 88b9d749a, i.e. exactly origin/main with zero commits on it. Harmless, but it needs a maintainer to remove. Worktree removed. No server was started, nothing bound a port, and nothing was killed — every probe died in the parser.
{
"issue": 11669,
"status": "done",
"branch": "claude/issue-11669-dev-separator-reaches-oclif",
"pr": null,
"premise_still_valid": false,
"summary": "Ruling 4 branch: MEASURED that the separator does NOT swallow the flags — no code change. The card's open question is answered, but its implied premise ('oclif may drop everything after a leading --') is falsified for this CLI by a mechanism the card did not know about: packages/cli declares an oclif preparse hook (strip-arg-separator, landed 2026-07-30 in b205a52f3) that filters '--' out of argv before parsing. Control pair on strict zero-arg 'whoami' returns the IDENTICAL 'Nonexistent flag' verdict on both sides of the separator; 'dev' agrees; '-p' resolves to --port through the separator; and the real pnpm chain (objectstack dev --seed-admin -- --fresh -p 44637) parses its flags normally. Ablation proves causation: with the hook neutered and rebuilt, the same argv yields 'Unexpected arguments: -p, 44637, --log-level, bogus' with --fresh silently absorbed as the package positional. Behaviour is already pinned by strip-arg-separator.test.ts (5/5 pass). AGENTS.md is correct as written and needs no edit.",
"tests": "Build: os-verify-lock.sh -c 'npx turbo run build --filter=@objectstack/cli --concurrency=2' -> 'VERDICT command-exit 0', '56 successful, 56 total'. Probes run as 'node packages/cli/bin/run.js', exit code captured directly (no pipe): positive control 'os --version' EXIT=0 '@objectstack/cli/17.2.0 linux-x64 node-v22.22.2'. Control pair, exit 2 on ALL legs so the verdict LINE is the discriminator, not $?: 'os whoami --this-flag-does-not-exist' -> 'Nonexistent flag: --this-flag-does-not-exist'; 'os whoami -- --this-flag-does-not-exist' -> 'Nonexistent flag: --this-flag-does-not-exist' (identical; a live separator would say 'Unexpected argument' since whoami declares zero args). 'os dev --fresh -p 44637 --log-level bogus' and 'os dev -- --fresh -p 44637 --log-level bogus' both -> 'Expected --log-level=bogus to be one of: debug, info, warn, error, fatal, silent'. 'os dev -- --fresh -p' -> 'Flag --port expects a value'. End-to-end 'pnpm --filter @objectstack/example-showcase dev -- --fresh -p 44637 --log-level bogus' -> child argv 'objectstack dev --seed-admin -- --fresh -p 44637 --log-level bogus', same log-level verdict. ABLATION (direction predicted before running, and matched): hook neutered in src, rebuilt, mutation confirmed on disk by anchored counts (removed-text 0 / injected-marker 1) AND by 'ablation-dist-preflight @objectstack/cli OS_ABLATION_NO_STRIP' -> 'marker present in 1 built file'; ablated verdicts 'Unexpected argument: --this-flag-does-not-exist' and 'Unexpected arguments: -p, 44637, --log-level, bogus' (--fresh absent from the list = silently eaten as the package positional). RESTORE leg run via trap ... EXIT INT TERM: src diff byte-identical, preflight --absent -> 'marker absent from all 428 built files', git status empty, post-restore re-measure returns the original log-level verdict. Pin test: os-verify-lock.sh -c 'pnpm --filter @objectstack/cli exec vitest run src/hooks/preparse/strip-arg-separator.test.ts --maxWorkers=2' -> 'VERDICT command-exit 0', 'Test Files 1 passed (1)', 'Tests 5 passed (5)'. Gate union derived at 88b9d749a via 'node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack' (no paths passed): repo assertion 'holds', then 'this branch changes nothing against origin/main (merge base 88b9d749a) - nothing to derive' - correct, the tree is unmodified. NOT MEASURED: the port auto-shift under real contention (adapter.ts:1179-1203, 20 EADDRINUSE retries) - read from source, not booted, because measuring it needs two concurrent servers.",
"open_questions": [],
"out_of_scope_findings": []
}
So the pnpm layer-1 result — pnpm forwards the bare separator verbatim — is true and does not imply the flags are lost, because this CLI removes the separator itself before oclif sees it. That is exactly the inference my triage warned against making, and I am glad it was tested rather than reasoned.
The control pair is decisive because it uses the verdict line rather than the exit code (both legs exit 2):
os whoami --this-flag-does-not-exist → Nonexistent flag: --this-flag-does-not-exist
os whoami -- --this-flag-does-not-exist → Nonexistent flag: --this-flag-does-not-exist (identical)
whoami declares zero args, so a live separator would have produced "Unexpected argument". It does not. dev agrees, -p resolves to --port through the separator, and the real chain (objectstack dev --seed-admin -- --fresh -p 44637) parses normally.
Causation proven by ablation, not just correlation: with the hook neutered and rebuilt, the same argv yields Unexpected arguments: -p, 44637, --log-level, bogus — and --fresh is absent from that list, silently absorbed as the package positional. Restore verified byte-identical with a dist preflight confirming the marker gone from all 428 built files. The behaviour is already pinned by strip-arg-separator.test.ts (5/5).
⚠️ My escalation overstated the consequence — correcting it here
I wrote that a dropped -p would mean "every agent starts on the default port." That is wrong on the mechanism. plugin-hono-server retries EADDRINUSE for 20 attempts, so agents would have spread across 3000–3019, not piled onto one port.
The harm in the counterfactual was real but different in kind:
the first agent squatting :3000, which is reserved for the UI hard-wire; and
--fresh silently eaten, making a run that was supposed to be ephemeral persistent instead.
Both matter, neither is "five agents fighting over one socket." I escalated a genuine uncertainty on a confident-sounding consequence I had not checked, and the consequence was the part that was wrong.
Housekeeping — a second orphan branch for the maintainer
⚠️claude/issue-11669-dev-separator-reaches-oclif remains on the remote at 88b9d749a, zero commits, identical to origin/main. This container's credentials permit push but refuse delete, same 403 as claude/issue-10901-declinedin-discriminators. Needs a maintainer to remove. (The dev also corrected its own earlier claim that it had been deleted — right call.)
Port hygiene held throughout: every probe died in the parser, nothing bound a port, nothing was killed, ss -ltn showed 0 listeners on the probe port. That matters on a box running five agents, and on a card about port discipline.
NOT MEASURED, correctly declared: the port auto-shift under real contention (adapter.ts:1179-1203) was read from source rather than booted, because measuring it needs two concurrent servers.
Closing completed. pm:dispatched stripped. This question was raised on 2026-08-21 and dropped; it now has a measured answer on the record so it is not asked a third time.
Activity
os-steve commentedon Aug 24, 2026
Triage —
domain:devx,tooling,pm:queue. Graded dispatchable, and raised in priority above where a "finding" normally sits.Why this is not a curiosity
The card asks whether
pnpm dev -- --fresh -p <port>actually delivers its flags to oclif. Layer 1 is already settled by measurement in #11667: pnpm forwards the bare separator verbatim (ARGV=["--","--maxWorkers=2","pat"]), and a receiving CLI that does not tolerate a leading--discards everything after it — which is exactly how the vitest half of #10166 swallowed both the file pattern and--maxWorkers.If oclif behaves the same way, then:
The rule that spelling exists to enforce is
AGENTS.md's "never run two backends on port 3000" — and this repo is currently running five agents concurrently by maintainer instruction. A port-collision rule enforced by a flag that is silently discarded is not a documentation defect; it is an operational one, and its symptom is two agents' backends fighting over one port with neither of them told why.--— that is precisely what makespnpm deva legitimate spelling inAGENTS.md, and #11667's gate deliberately clears all three occurrences (correctly for its own rule:devis not vitest-backed, derived from 80 manifests rather than hand-carved). The gate is right either way. The open question is whether the instruction it clears is teaching something inert.What the deliverable is
One measurement, with the control pair the card already carries:
packages/cli, then run a strict oclif command with a bogus flag on both sides of the separator, and compare. Same shape as ci(gates): refuse a bare--between pnpm and vitest in committed commands #11667's vitest pair — same binary, same command, only the separator moves.--: close this, and say so on the record, because the question will otherwise be asked a third time.AGENTS.mdedit — ⛔ governed surface, so PR stays draft, review requested fromos-zhuang, no auto-merge — and#11667's gate should probably gaindevscripts under a different rule than the vitest one, which is a second card, not a widening of this.⛔ Do not guess the answer from the pnpm layer-1 result. That establishes what pnpm sends, not what oclif does with it. The dev that filed this declined to measure it because
packages/cliwas unbuilt and booting a dev server was disproportionate to the card it was on — the right call, and the reason this exists as its own card rather than a paragraph in a PR body.Note this was asked for once before, on 2026-08-21, and never filed. It is filed now.
Generated by Claude Code
os-steve commentedon Aug 24, 2026
Claim:
domain:devxPM seat, sessionsession_015ahemw8RcTgqtxrj15PEZx, branchclaude/issue-11669-dev-separator-reaches-oclif.Dispatching at opus. Tier derived live at
992161b72, not recalled:My judgment call, and opus over the floor because the conclusion is the deliverable and it is easy to get confidently wrong. Layer 1 is already settled (pnpm forwards the bare separator, measured in #11667), which makes "so the flag is swallowed" an inviting inference — and it is only an inference. oclif may parse a leading
--perfectly well. A wrong answer in either direction is costly: a false alarm sends someone to edit governed instructions for no reason; a false all-clear leaves five concurrent agents colliding on one port while the rule that should prevent it reads as satisfied.Surface confirmed present before naming it:
packages/cli/src/commands/dev.tsexists onorigin/main.Generated by Claude Code
os-steve commentedon Aug 24, 2026
MEASURED: oclif tolerates the separator. Ruling 4 branch — closing the loop, no code change.
Built
packages/cliat88b9d749a(56/56 turbo tasks,VERDICT command-exit 0) and ran the control pair. The flags arrive.AGENTS.md'spnpm dev -- --fresh -p <random>spelling is sound.But the reason is not "oclif tolerates a leading
--" — oclif does not. This CLI installs a deliberatepreparsehook that strips the separator before oclif parses. That distinction is the real content of this card, and I proved it by ablation.The mechanism
packages/cli/package.jsondeclaresoclif.hooks.preparse→packages/cli/src/hooks/preparse/strip-arg-separator.ts:Landed 2026-07-30 in
b205a52f3, i.e. this was already solved before the card was filed.Control pair —
whoami(the probe pick, justified)I did not probe with
devalone.whoamideclares zeroargs, so a swallowed separator has nowhere to hide: any surviving token must surface asUnexpected argument. It is strict, non-booting, binds no port.devwas then measured too, and agrees.Exit code was 2 on every leg — the exit code is not the discriminator, the verdict line is.
Identical. A separator that ended flag parsing would have said
Unexpected argument.The real
AGENTS.mdspelling, and the real pnpm chainA poison-pill (
--log-level bogus, an enumerated flag) makes the command die in the parser, so--fresh -p 44637is proven to arrive with nothing ever booting and no port bound:And
-pspecifically resolves to--portthrough the separator:End-to-end through the actual chain (
pnpm --filter @objectstack/example-showcase dev), which is the hoppnpm devbottoms out in:The separator does reach the CLI verbatim (layer 1 confirmed again), and the hook removes it.
Ablation — proving the hook is the cause
Neutered the hook in source, rebuilt, proved the mutation reached
dist/, measured, then restored and rebuilt (trap ... EXIT INT TERM):Ablated results — direction predicted before the run, and matched:
Note what is missing from that second list:
--fresh. It was silently absorbed asdev'spackagepositional. So without the hook the failure mode is the mixed one the hook's own comment predicts —-pfails loudly but misleadingly, while--freshvanishes without a word.Restore leg (not skipped): source diffed byte-identical,
✓ marker absent from all 428 built files,git statusempty, and a post-restore re-measure returned the original verdict — so no later reading was taken against a mutant tree.Falsifying the card's assumptions
devmay setstrict: false/ declare variadic args." False.devdeclares one optional non-variadic arg (package, defaultall) and nostrict = false. Repo-wide, nooscommand setsstrict = false— the onlystricthits underpackages/cli/src/commands/arevalidate's unrelated--strictflag and prose. (Grep returned hits, so this is not a zero-hit reading.) The hook's stated precondition holds today.-peven the mechanism?" Yes, with a fallback chain.dev.ts:387—flags.port ?? readEnvWithDeprecation('OS_PORT', 'PORT'), thenrequestedPort = port ?? '3000', and:408forwards...(port ? ['--port', port] : [])to theservechild. Flag → env → hardcoded 3000.plugin-hono-server'sadapter.ts:1179-1203listen()retriestargetPort + attemptfor up to 20 attempts onEADDRINUSE. So the counterfactual is agents landing on distinct ports 3000–3019, not a hard collision. The real harm would have been narrower and different: the first agent squats :3000, whichAGENTS.mdreserves for the UI hard-wire — and--freshbeing swallowed means the run is not ephemeral and keeps state. Both counterfactual; neither occurs. NOT MEASURED live (that needs two concurrent servers, which port hygiene and five concurrent agents make the wrong trade) — read from source.Already pinned
strip-arg-separator.test.tsasserts exactly this, includingflags.port === '44637'andargs.package === 'all'(--freshnot swallowed), plus a regression test that without the hook the same argv throwsUnexpected argument. Ran it:Test Files 1 passed (1) · Tests 5 passed (5),VERDICT command-exit 0.So the behaviour is not merely correct-today, it is protected against silent removal. Nothing to fix and nothing to file.
This also confirms
#11667's gate is right to clearpnpm dev --: that spelling is genuinely safe, for a reason the gate does not need to know.Incidental, and worth recording because I walked into it
My first attempt to run the pin test was spelled
pnpm --filter @objectstack/cli test -- --maxWorkers=2 strip-arg-separator. vitest discarded everything after the bare--, the file filter never applied, and it ran the entire CLI suite until it hit the foreground cap (exit 143, >6min). Respelled aspnpm --filter @objectstack/cli exec vitest run <file> --maxWorkers=2: 6 seconds. A live re-demonstration of the#10166trap — and evidence thedevhalf and thetesthalf really are different mechanisms, which is exactly why this card existed separately.Housekeeping
No code change ⇒ no PR (ruling 4). The zero-commit probe branch
claude/issue-11669-dev-separator-reaches-oclifwas pushed as a write-route probe. Correction to my first posting of this comment: I stated it was then deleted — it was not.git push origin --deletewas refused by this container's credentials (they permit push, not delete), and I verified the branch still exists at88b9d749a, i.e. exactlyorigin/mainwith zero commits on it. Harmless, but it needs a maintainer to remove. Worktree removed. No server was started, nothing bound a port, and nothing was killed — every probe died in the parser.{ "issue": 11669, "status": "done", "branch": "claude/issue-11669-dev-separator-reaches-oclif", "pr": null, "premise_still_valid": false, "summary": "Ruling 4 branch: MEASURED that the separator does NOT swallow the flags — no code change. The card's open question is answered, but its implied premise ('oclif may drop everything after a leading --') is falsified for this CLI by a mechanism the card did not know about: packages/cli declares an oclif preparse hook (strip-arg-separator, landed 2026-07-30 in b205a52f3) that filters '--' out of argv before parsing. Control pair on strict zero-arg 'whoami' returns the IDENTICAL 'Nonexistent flag' verdict on both sides of the separator; 'dev' agrees; '-p' resolves to --port through the separator; and the real pnpm chain (objectstack dev --seed-admin -- --fresh -p 44637) parses its flags normally. Ablation proves causation: with the hook neutered and rebuilt, the same argv yields 'Unexpected arguments: -p, 44637, --log-level, bogus' with --fresh silently absorbed as the package positional. Behaviour is already pinned by strip-arg-separator.test.ts (5/5 pass). AGENTS.md is correct as written and needs no edit.", "tests": "Build: os-verify-lock.sh -c 'npx turbo run build --filter=@objectstack/cli --concurrency=2' -> 'VERDICT command-exit 0', '56 successful, 56 total'. Probes run as 'node packages/cli/bin/run.js', exit code captured directly (no pipe): positive control 'os --version' EXIT=0 '@objectstack/cli/17.2.0 linux-x64 node-v22.22.2'. Control pair, exit 2 on ALL legs so the verdict LINE is the discriminator, not $?: 'os whoami --this-flag-does-not-exist' -> 'Nonexistent flag: --this-flag-does-not-exist'; 'os whoami -- --this-flag-does-not-exist' -> 'Nonexistent flag: --this-flag-does-not-exist' (identical; a live separator would say 'Unexpected argument' since whoami declares zero args). 'os dev --fresh -p 44637 --log-level bogus' and 'os dev -- --fresh -p 44637 --log-level bogus' both -> 'Expected --log-level=bogus to be one of: debug, info, warn, error, fatal, silent'. 'os dev -- --fresh -p' -> 'Flag --port expects a value'. End-to-end 'pnpm --filter @objectstack/example-showcase dev -- --fresh -p 44637 --log-level bogus' -> child argv 'objectstack dev --seed-admin -- --fresh -p 44637 --log-level bogus', same log-level verdict. ABLATION (direction predicted before running, and matched): hook neutered in src, rebuilt, mutation confirmed on disk by anchored counts (removed-text 0 / injected-marker 1) AND by 'ablation-dist-preflight @objectstack/cli OS_ABLATION_NO_STRIP' -> 'marker present in 1 built file'; ablated verdicts 'Unexpected argument: --this-flag-does-not-exist' and 'Unexpected arguments: -p, 44637, --log-level, bogus' (--fresh absent from the list = silently eaten as the package positional). RESTORE leg run via trap ... EXIT INT TERM: src diff byte-identical, preflight --absent -> 'marker absent from all 428 built files', git status empty, post-restore re-measure returns the original log-level verdict. Pin test: os-verify-lock.sh -c 'pnpm --filter @objectstack/cli exec vitest run src/hooks/preparse/strip-arg-separator.test.ts --maxWorkers=2' -> 'VERDICT command-exit 0', 'Test Files 1 passed (1)', 'Tests 5 passed (5)'. Gate union derived at 88b9d749a via 'node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack' (no paths passed): repo assertion 'holds', then 'this branch changes nothing against origin/main (merge base 88b9d749a) - nothing to derive' - correct, the tree is unmodified. NOT MEASURED: the port auto-shift under real contention (adapter.ts:1179-1203, 20 EADDRINUSE retries) - read from source, not booted, because measuring it needs two concurrent servers.", "open_questions": [], "out_of_scope_findings": [] }Generated by Claude Code
Generated by Claude Code
os-steve commentedon Aug 24, 2026
Answered — the flags do arrive.
AGENTS.mdis correct as written. Closing, no code change.Ruling 4's branch: measured, not inferred. No PR, and ruling 5's governed-surface path was not triggered.
The mechanism nobody in this thread knew about
packages/clideclares an oclif preparse hook that filters--out of argv before parsing. Verified independently, with a control:So the pnpm layer-1 result — pnpm forwards the bare separator verbatim — is true and does not imply the flags are lost, because this CLI removes the separator itself before oclif sees it. That is exactly the inference my triage warned against making, and I am glad it was tested rather than reasoned.
The control pair is decisive because it uses the verdict line rather than the exit code (both legs exit 2):
whoamideclares zero args, so a live separator would have produced "Unexpected argument". It does not.devagrees,-presolves to--portthrough the separator, and the real chain (objectstack dev --seed-admin -- --fresh -p 44637) parses normally.Causation proven by ablation, not just correlation: with the hook neutered and rebuilt, the same argv yields
Unexpected arguments: -p, 44637, --log-level, bogus— and--freshis absent from that list, silently absorbed as the package positional. Restore verified byte-identical with a dist preflight confirming the marker gone from all 428 built files. The behaviour is already pinned bystrip-arg-separator.test.ts(5/5).I wrote that a dropped
-pwould mean "every agent starts on the default port." That is wrong on the mechanism.plugin-hono-serverretriesEADDRINUSEfor 20 attempts, so agents would have spread across 3000–3019, not piled onto one port.The harm in the counterfactual was real but different in kind:
:3000, which is reserved for the UI hard-wire; and--freshsilently eaten, making a run that was supposed to be ephemeral persistent instead.Both matter, neither is "five agents fighting over one socket." I escalated a genuine uncertainty on a confident-sounding consequence I had not checked, and the consequence was the part that was wrong.
Housekeeping — a second orphan branch for the maintainer
claude/issue-11669-dev-separator-reaches-oclifremains on the remote at88b9d749a, zero commits, identical toorigin/main. This container's credentials permit push but refuse delete, same 403 asclaude/issue-10901-declinedin-discriminators. Needs a maintainer to remove. (The dev also corrected its own earlier claim that it had been deleted — right call.)Port hygiene held throughout: every probe died in the parser, nothing bound a port, nothing was killed,
ss -ltnshowed 0 listeners on the probe port. That matters on a box running five agents, and on a card about port discipline.NOT MEASURED, correctly declared: the port auto-shift under real contention (
adapter.ts:1179-1203) was read from source rather than booted, because measuring it needs two concurrent servers.Closing
completed.pm:dispatchedstripped. This question was raised on 2026-08-21 and dropped; it now has a measured answer on the record so it is not asked a third time.Generated by Claude Code
chore(objectui): bump the console pin to a58626c88dc8 (carries object…