Repository navigation
Commit 583b024
fix(cli): os dev shifts off a busy port before it compiles, so the config reads the port the server binds (#26330)
<!-- claude-projects-attribution: {"github_login":"os-project-manager"}
-->
_Requested by **Leehom** · [project
thread](https://claude.ai/code/project/chan_01TenzsRopZj4jMbvjX8uz1X?thread=cmsg_01TenzsRopZj4jMbvjX8uz1XBnXB7E8hqiCjc4qEdSqTRv)_
Fixes #26270
Clause-②: no
Metadata-protocol: none
## What changed
When `os dev` shifts off a busy port, the config now reads the port the
server binds, through the one port channel #26243 set up (`portFlagEnv`,
`OS_PORT`/`PORT`). No second copy of the precedence: the requested port
is the expression `dev.ts` already had (`port ?? '3000'`), the walk is
`serve.ts`'s own `getAvailablePort`, and the notice is `serve.ts`'s own
wording, extracted into `formatDevPortShiftNotice`.
- **`os dev` (`packages/cli/src/commands/dev.ts`).** Before the compile
(the first process that evaluates the config), the parent runs the same
walk from the same requested port. On a shift it prints the shift notice
(stderr), exports the bound port with `portFlagEnv` (so the compile, the
watch recompiles and the `serve` child read it), and forwards it as the
child's `--port`, so the child binds it without walking again and a
restart keeps it. An exhausted walk is left to the child, which repeats
it and prints `formatExhaustedPortSearchNotice` as before.
- **`os serve` (`packages/cli/src/commands/serve.ts`).** The development
auto-shift branch now exports the port it shifted to with `portFlagEnv`,
before `loadProjectEnvFiles`, the config load, and the direct `os serve
--dev` boot compile (#26242). That is the same branch whether it was
entered through `--dev` or `NODE_ENV=development`.
- `utils/port-contract.ts`: `portFlagEnv`'s caller list names the shift.
`examples/app-showcase/src/system/self-url.ts` is unchanged: its header
claims stay true.
## Measured at the public door (`examples/app-showcase`, a TCP listener
holding the requested port)
| boot | `main` (after #26268) | this branch |
|---|---|---|
| `os dev --fresh -p 41310` | shift notice 41310 → 41311;
`/api/v1/meta/connector/showcase_status_api` `baseUrl` `:41310`; `POST
/api/v1/automation/showcase_declarative_connector_ping/trigger` **400
FLOW_FAILED** | (41330 → 41331, 41400 → 41401 after the main merge)
`baseUrl` `:41331` / `:41401`; trigger **200**, `ping.body.data.status:
"ok"` |
| `OS_PORT=41320 os dev --fresh` (no flag) | 41320 → 41321; `baseUrl`
`:41320`; trigger **400** | (41340 → 41341) `baseUrl` `:41341`; trigger
**200** |
| `os serve --dev -p 41370` | 41370 → 41371; trigger **400** | (41350 →
41351) trigger **200**; after merging #26289: (41390 → 41391) `baseUrl`
`:41391`, trigger **200** |
| `NODE_ENV=development os serve -p 41380` | 41380 → 41381; trigger
**400** | (41360 → 41361) trigger **200** |
The `main` column's failure text is
`connector_action(showcase_status_api.request) failed: resilientFetch:
request timed out after 30000ms` because the probe's holder accepts and
never answers; with nothing answering, the card measured `fetch failed`.
The ruling's scope note on `os serve` without `--dev`: it does shift,
under `NODE_ENV=development` only (`portAutoShiftAllowed`), and it is
the same branch through the same channel, so it is fixed by the same
line. Production `os serve` still refuses a busy port, unchanged.
## The pin
`packages/cli/test/dev-connector-env-parity.integration.test.ts` gains a
third boot: `os dev -p N` with N held for the whole boot by an HTTP
neighbour answering 503. The bound port is read from the child's own
banner. Three arms: the materialized connector `baseUrl`, the served
`/api/v1/meta/connector/NAME` `baseUrl`, and a flow pinging
`/api/v1/health` through the connector (`fxp_ping`, the fixture's
`showcase_declarative_connector_ping`) answering 200.
- **Red first** (commit 1084d2e test + 774eb26 neighbour, fix sources
restored from before bc2d71e, cli rebuilt): 3 failed, 3 passed.
`expected 'http://127.0.0.1:37609' to be 'http://127.0.0.1:37610'`
(twice) and `FLOW_FAILED … connector_action(fxp_self.ping) failed:
health answered 503: expected 400 to be 200`. The earlier #26130/#26243
arms stayed green.
- **Green** with the fix: 6/6, and 9/9 together with
`serve-dev-connector-env-parity.integration.test.ts` after merging
`main`.
- The first red run hung: `holdPort()` from the shared helper never
answers, so its `release()` waited on the CLOSE_WAIT socket the ping
left behind (the hook timed out at 600 s). The file owns an answering
neighbour instead, as `serve-port-drift-notice.e2e.test.ts` does; no
draw is added (the port is held, not released, so the
`released-port-production-spawn` ledger is unchanged).
## Tests and gates (head 42bd366)
- `pnpm --filter @objectstack/cli exec vitest run --project unit
--maxWorkers=2`: lock `VERDICT command-exit 0`, Test Files 334 passed,
Tests 4884 passed, at 42bd366 (4878 before the main merge, at
4cb7cd3).
- `--project integration` on the two parity files: 2 files, 9 tests
passed.
- `pnpm --filter @objectstack/cli typecheck` (tsc plus
`check:test-typecheck`, whose project lists this test file): exit 0.
- ESLint `--no-inline-config` on the four touched sources: exit 0.
- `dispatch-gates --commands`: 69 families, all run, `--ran`
reconciliation `69 derived … 68 run, 1 NOT-MEASURED`. Every run exited 0
except: `check:dual-build-cjs-loads` exit 3 (`Run pnpm build first`, NOT
MEASURED, CI builds and runs it), and `check-plugin-teardown-shape
--self-test` exit 3 on the shallow checkout, 0 after `git fetch origin
621a487` (its pinned fixture).
## Acceptance notes
- **Residual window.** The parent's probe releases the port before the
child binds it. If something takes it in between, the child shifts once
more, says so, and exports its own port for its config, but the artifact
the parent compiled (`/api/v1/meta`) keeps the probed port until the
next recompile. Stated in the code and the changeset.
- **`--port` text notice.** When the parent shifts, the child receives
the bound port as a number, so `serve`'s `portTextReadNotice` (for text
like `08080` read as 8080) does not print on that boot. It still prints
whenever no shift happens.
- **Test instrument.** `holdPort()` in `test/helpers/serve-process.ts`
cannot be released once something has connected to it (CLOSE_WAIT,
`server.close()` never calls back). Not filed: no product defect, no
reach outside tests. Carrier: none.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01NQ4sTujBVMFFGmdXcRmdd5)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent da99a4b commit 583b024
5 files changed
Lines changed: 241 additions & 18 deletions
File tree
- .changeset
- packages/cli
- src
- commands
- utils
- test
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
54 | 54 | | |
55 | 55 | | |
56 | 56 | | |
57 | | - | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
58 | 61 | | |
59 | 62 | | |
60 | 63 | | |
| |||
475 | 478 | | |
476 | 479 | | |
477 | 480 | | |
478 | | - | |
| 481 | + | |
479 | 482 | | |
480 | 483 | | |
481 | 484 | | |
| |||
531 | 534 | | |
532 | 535 | | |
533 | 536 | | |
| 537 | + | |
| 538 | + | |
| 539 | + | |
| 540 | + | |
| 541 | + | |
| 542 | + | |
| 543 | + | |
| 544 | + | |
| 545 | + | |
| 546 | + | |
| 547 | + | |
| 548 | + | |
| 549 | + | |
| 550 | + | |
| 551 | + | |
| 552 | + | |
| 553 | + | |
| 554 | + | |
| 555 | + | |
| 556 | + | |
| 557 | + | |
| 558 | + | |
| 559 | + | |
| 560 | + | |
| 561 | + | |
| 562 | + | |
| 563 | + | |
| 564 | + | |
| 565 | + | |
| 566 | + | |
| 567 | + | |
| 568 | + | |
| 569 | + | |
| 570 | + | |
| 571 | + | |
| 572 | + | |
| 573 | + | |
534 | 574 | | |
535 | 575 | | |
536 | 576 | | |
| |||
781 | 821 | | |
782 | 822 | | |
783 | 823 | | |
784 | | - | |
785 | 824 | | |
786 | 825 | | |
787 | 826 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
468 | 468 | | |
469 | 469 | | |
470 | 470 | | |
| 471 | + | |
| 472 | + | |
| 473 | + | |
| 474 | + | |
| 475 | + | |
| 476 | + | |
| 477 | + | |
| 478 | + | |
| 479 | + | |
| 480 | + | |
| 481 | + | |
| 482 | + | |
| 483 | + | |
| 484 | + | |
| 485 | + | |
| 486 | + | |
| 487 | + | |
| 488 | + | |
| 489 | + | |
| 490 | + | |
471 | 491 | | |
472 | 492 | | |
473 | 493 | | |
| |||
2344 | 2364 | | |
2345 | 2365 | | |
2346 | 2366 | | |
2347 | | - | |
2348 | | - | |
2349 | | - | |
2350 | | - | |
2351 | | - | |
2352 | | - | |
2353 | | - | |
2354 | | - | |
| 2367 | + | |
| 2368 | + | |
| 2369 | + | |
| 2370 | + | |
| 2371 | + | |
| 2372 | + | |
| 2373 | + | |
| 2374 | + | |
| 2375 | + | |
| 2376 | + | |
2355 | 2377 | | |
2356 | 2378 | | |
2357 | 2379 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
425 | 425 | | |
426 | 426 | | |
427 | 427 | | |
| 428 | + | |
| 429 | + | |
| 430 | + | |
| 431 | + | |
428 | 432 | | |
429 | 433 | | |
430 | 434 | | |
| |||
0 commit comments