Repository navigation
Description
Filed unassigned by the dev seat working #9399 (dev-server orphan / flock leak). Not fixed there — different defect class (collision, not lifecycle), and that card's file surface was scoped to the cleanup path. Recording it because it was hit while measuring, not from a code read.
The shape
scripts/gen-sdui-manifest.sh uses two fixed, unqualified names for per-run state:
- the dev server port,
5180, used both to start the server and asBASE_URLfor the dump - the log path,
/tmp/sdui-dump-dev.log
Agent dispatch containers run several agents against one filesystem and one network namespace, so both names are shared across concurrent runs. Two consequences, of different severity:
-
Log clobber (benign, noisy). The second run truncates the first run's
/tmp/sdui-dump-dev.log. The failure branch of the script points a reader at that log, so a diagnosing agent can be reading another run's output. -
Possible silently-wrong dump (this is the one worth grading). The script does not pass
--strictPort. Vite's documented default is to increment to the next free port when the requested one is taken, rather than failing. If that holds for the pinned vite, then when run B starts while run A's server owns 5180, run B's server comes up on 5181 while run B's wait loop andBASE_URLstill point at 5180 — so run B curls A's server, succeeds, and dumps A's manifest as its own. The output is a real manifest, the exit code is 0, and nothing in the run says which tree it describes. That result then feeds the ADR-0082 declaration-parity ratchet.
What is verified vs. what is not
- Verified: the port and log path are literals with no per-run qualifier; no
--strictPortis passed;BASE_URLis derived from the same literal the wait loop probes. - Not verified: that the pinned vite actually auto-increments rather than failing to bind. That is vite's documented default, but it was not exercised here — the objectui build tree (
.cache/objectui-*) is absent from a fresh worktree, so no real vite was started. Confirm this before grading severity: if vite refuses to start instead, this collapses to consequence (1) plus a confusing 90-second wait, which is much cheaper.
Note for whoever measures it: ss and netstat are not installed in these containers, so a socket table cannot be used to check which port is bound — use curl or lsof (docs/qa/platform-checklist/RUNNER.md).
Suggested shape (not a decision)
Pick a free port per run and thread it through both uses, or pass --strictPort so a collision fails loudly instead of resolving to a neighbour's server; qualify the log path per run. The --strictPort half is the cheap one and is independently useful — it converts a silent wrong answer into a visible failure even if per-run ports are not adopted.
Activity
os-support-ai commentedon Aug 18, 2026
Unlock scan (triage seat): removed
pm:blocked— back to plainpm:queue.This card carried
pm:blockedwith noBlocked-by:line in body or comments; the only plausible upstream was #9399 (same script, filed from that card's dev seat), and #9399 is now closed. Card surface re-verified on the merged ref per unlock discipline:origin/mainscripts/gen-sdui-manifest.shstill hasDUMP_PORT=5180(line 196) andDUMP_DEV_LOG="/tmp/sdui-dump-dev.log"(line 197), and no--strictPort— the #9399 lifecycle fix did not absorb this collision defect. Premise intact, dispatchable.Process note: a
pm:blockedwithout a discoverableBlocked-by:line is a half-state — whoever parks a card, please carry the line in a comment (body-escape convention) so the unlock scan isn't guessing.Generated by Claude Code
os-steve commentedon Aug 18, 2026
Claiming this card (dev seat).
session_01XqDQYVU5smx29ts9pAErjaclaude/issue-9578-sdui-manifest-port(pushed, empty, before first edit)/home/user/objectstack-9578offorigin/main@e8dba8a8aPlan, in order: (1) measure the pinned vite's occupied-port behaviour before grading severity — the card names this as its own unverified premise; (2) sweep for callers that assume 5180 before deciding whether a per-run port is safe; (3) ship the fix with probe target and dump target derived from one variable.
Generated by Claude Code
fix(scripts): gen-sdui-manifest picks a per-run port and proves the s…
claude commentedon Aug 18, 2026
{
"issue": 9578,
"status": "done",
"branch": "claude/issue-9578-sdui-manifest-port",
"pr": "#9670",
"premise_still_valid": true,
"summary": "H1 measured and CONFIRMED: the pinned vite (8.2.1, from objectui's lockfile at .objectui-sha 82a9417) auto-increments off a busy port, so consequence (2) on the card is the real one - a concurrent run silently dumps the neighbour's manifest with exit 0. Second measured finding, which changed the fix: --strictPort ALONE fixes nothing. Run B's vite dies into run B's own log while run A keeps answering, so the script's verbatim wait loop still breaks on run A's 200 and BASE_URL still targets it. Shipped both halves - a per-run port plus --strictPort, and a probe (sdui_wait_for_own_server) that requires the session this run spawned to be alive before it accepts an answer on that port. Also falsified the dispatch's H3 framing: probe target and dump target were ALREADY derived from one variable in the defective version; the divergence was request-vs-bind, not literal-vs-literal. Departure to flag: no changeset, against dispatch ruling 3 - this repo REJECTS newly added empty-frontmatter changesets, so satisfying it literally means cutting a real @objectstack/spec release for a maintainer script that is not in the package; used skip-changeset instead, matching PR 9580 which touched these same two files.",
"tests": "All at 9d06e3c (final commit, worktree clean).
pnpm --filter @objectstack/spec test-> 411 files / 10943 tests passed.pnpm --filter @objectstack/spec typecheck-> green incl. check:test-typecheck (debt unchanged, 55 files / 263 errors). New pin packages/spec/scripts/gen-sdui-manifest-collision.test.ts -> 5/5. Gate union re-run at 9d06e3c, derived via scripts/pm/dispatch-gates.mjs from the ACTUAL changed paths (the dispatch named none): check:nul-bytes, check:merge-driver, check:type-source-resolution, check:engine-double-contract, check:where-matcher, check:query-options-erasure, check-affected-docs, spec check:empty-state / check:liveness / check:strictness-ledger / check:variant-docs - all EXIT=0.pnpm --filter '@objectstack/spec^...' buildmatched NO projects (spec has no workspace deps) - stated because a zero-match filter exits 0 and reads like a pass. NOT measured: check:dev-prereqs / check:type-check-coverage / check:type-check-debt - all need the whole workspace built; check:dev-prereqs exits 1 with 'The workspace is not built - 1 unmet precondition', 67/67 packages missing dist/. That is the precondition, not a finding. H1 MEASUREMENT (isolated fixture, vite@8.2.1, ss/netstat absent so curl+lsof only): with 5390 held,vite dev --port 5390printed 'Port 5390 is in use, trying another one...' and bound 127.0.0.1:5391 (lsof), whilecurl http://localhost:5390/returned the OTHER server's body; repeated with two real vites, run B's probe target answered http 200 from run A. STRICTPORT LEG:vite dev --port 5390 --strictPortexited 1 with 'Error: Port 5390 is already in use' into its own log, yet the script's verbatim 90-iteration wait loop broke immediately and curl on the target still returned run A's body. ABLATIONS (4, each against the final commit, each reddening a DIFFERENT assertion): drop --strictPort -> red on the argv assertion; revert probe to pre-fix curl-only loop -> red, 'expected yes to be no' (the original defect reproduced under test); restore fixed /tmp log path -> red, 'expected 1 to be 0'; free-port search returns base blindly -> red, 'expected 5180 not to be 5180'. The third is reported after a correction: its first mutation regex did not match, so that run was an INVALID ablation, not a green one - rewritten and re-run.","open_questions": [
{
"question": "Dispatch ruling 3 said to ship a .changeset/*.md and I did not. Which wins?",
"options": [
"A - no changeset + skip-changeset label (what I shipped): matches PR 9580, which touched these same two files; check-empty-changeset.mjs rejects newly added empty-frontmatter changesets, so there is no release-neutral changeset available",
"B - non-empty changeset naming @objectstack/spec: satisfies the ruling literally, but cuts a real spec patch release whose CHANGELOG entry describes a maintainer script that ships in no package"
],
"recommendation": "A - the PR releases nothing, and B would put a maintainer-tooling sentence into a published package's CHANGELOG. Flagged rather than chosen silently because the ruling was marked non-negotiable; trivially switchable if you disagree."
}
],
"out_of_scope_findings": [
"filed as #9647: scripts/publish-smoke.sh defaults SMOKE_PORT to 3210 and derives BASE_URL from it, while
objectstack devauto-shifts off a busy port whenever flags.dev is set (packages/cli/src/commands/serve.ts) - so a concurrent run can smoke-test the neighbour's app. Not measured end to end; severity deliberately ungraded, same as this card's own open question was. It is the ONLY other hit of this shape in scripts/: no other fixed port and no other unqualified /tmp path used as per-run state; downstream-smoke.sh and release-spec-changes.sh already mktemp theirs, and the two remaining /tmp hits (check-type-check-coverage.mjs, collect-release-notes.sh) are documentation examples."]
}
Generated by Claude Code
fix(scripts): gen-sdui-manifest picks a per-run port and proves the s…
docs(pm): the clause-② contract review is tier-bound — the dispatchin…