dev-headless silently talks to whatever already holds its port #119

Closed
opened 2026-08-19 05:25:38 +00:00 by logan · 1 comment
Collaborator

Report

dev-headless.sh does not check whether anything already holds its
port. If something does, the new app starts, fails to bind, and every
subsequent curl/playwright-cli call goes to the other process —
so the harness reports facts about an app nobody asked for.

How it presented, which is the reason this is worth a check rather
than a paragraph:

$ make sandbox-seed NAME=default
seed-sandbox: registering library .../test_data/music_library_test
seed-sandbox: waiting for the scan to reach 31 tracks
curl: (22) The requested URL returned error: 422
jq: error (at <stdin>:0): Cannot index string with string ("trackCount")
make: *** [Makefile:104: sandbox-seed] Error 5

and the call underneath answers:

$ curl -sS -X POST localhost:34115/wails/runtime -H 'Content-Type: application/json' \
    -d '{"object":0,"method":0,"args":{"call-id":"x",
         "methodName":"yellowjacket/backend/library.Library.GetAllLibrariesWithTrackCounts","args":[]}}'
{"message":"could not get libraries: SQL logic error: no such table: libraries (1)",
 "kind":"RuntimeError"}

"no such table: libraries" against a fresh YJ_HOME reads exactly
like applySchema or staleshape.go having gone wrong — which is a
frightening place to start looking, and is where I started. It is not
that. The port was held by a yj-dev from a deleted worktree that had
been running for two days:

$ ss -lptn 'sport = :34115'
LISTEN 0 4096 127.0.0.1:34115 users:(("yj-dev",pid=1003605,fd=46))
$ ls -la /proc/1003605/cwd
... -> /home/logan/.../unruly-goat (deleted)

Its database was deleted with its worktree, hence the missing table.
Meanwhile .dev/app.log was zero bytes and .dev/home-fresh was
empty — the app the script had just launched had exited, and nothing
said so.

Findings

  • scripts/dev-headless.sh writes .dev/app.pid and returns without
    confirming the process it started is the one answering on the port.
  • The health check that does exist is satisfied by any app on that
    port, which is precisely the failure.
  • This is likelier than it looks on this setup: several worktrees of
    this repo share the default port, and make dev-stop only kills the
    pid in that worktree's .dev/app.pid. Deleting a worktree while its
    app runs orphans the process with nothing left to stop it.

Direction

Refuse to start rather than warn. Before launching, if the port is
already listening, print the holder (ss -lptn gives the pid, and
/proc/<pid>/cwd gives which checkout it belongs to — including
(deleted)) and exit non-zero. A --port is already supported for the
second app case, so nothing legitimate needs the current behaviour.

Worth a second, cheaper guard: after launch, fail if .dev/app.log is
empty and the pid is gone, which is the "it exited immediately" case
this hid.

Do not use pkill -f in whatever fixes this, for the reason
CLAUDE.md already gives.

**Report** `dev-headless.sh` does not check whether anything already holds its port. If something does, the new app starts, fails to bind, and every subsequent `curl`/`playwright-cli` call goes to the **other** process — so the harness reports facts about an app nobody asked for. **How it presented**, which is the reason this is worth a check rather than a paragraph: ``` $ make sandbox-seed NAME=default seed-sandbox: registering library .../test_data/music_library_test seed-sandbox: waiting for the scan to reach 31 tracks curl: (22) The requested URL returned error: 422 jq: error (at <stdin>:0): Cannot index string with string ("trackCount") make: *** [Makefile:104: sandbox-seed] Error 5 ``` and the call underneath answers: ``` $ curl -sS -X POST localhost:34115/wails/runtime -H 'Content-Type: application/json' \ -d '{"object":0,"method":0,"args":{"call-id":"x", "methodName":"yellowjacket/backend/library.Library.GetAllLibrariesWithTrackCounts","args":[]}}' {"message":"could not get libraries: SQL logic error: no such table: libraries (1)", "kind":"RuntimeError"} ``` "no such table: libraries" against a **fresh** `YJ_HOME` reads exactly like `applySchema` or `staleshape.go` having gone wrong — which is a frightening place to start looking, and is where I started. It is not that. The port was held by a `yj-dev` from a *deleted* worktree that had been running for two days: ``` $ ss -lptn 'sport = :34115' LISTEN 0 4096 127.0.0.1:34115 users:(("yj-dev",pid=1003605,fd=46)) $ ls -la /proc/1003605/cwd ... -> /home/logan/.../unruly-goat (deleted) ``` Its database was deleted with its worktree, hence the missing table. Meanwhile `.dev/app.log` was **zero bytes** and `.dev/home-fresh` was **empty** — the app the script had just launched had exited, and nothing said so. **Findings** - `scripts/dev-headless.sh` writes `.dev/app.pid` and returns without confirming the process it started is the one answering on the port. - The health check that does exist is satisfied by *any* app on that port, which is precisely the failure. - This is likelier than it looks on this setup: several worktrees of this repo share the default port, and `make dev-stop` only kills the pid in *that* worktree's `.dev/app.pid`. Deleting a worktree while its app runs orphans the process with nothing left to stop it. **Direction** Refuse to start rather than warn. Before launching, if the port is already listening, print the holder (`ss -lptn` gives the pid, and `/proc/<pid>/cwd` gives which checkout it belongs to — including `(deleted)`) and exit non-zero. A `--port` is already supported for the second app case, so nothing legitimate needs the current behaviour. Worth a second, cheaper guard: after launch, fail if `.dev/app.log` is empty and the pid is gone, which is the "it exited immediately" case this hid. **Do not use `pkill -f`** in whatever fixes this, for the reason CLAUDE.md already gives.
logan added the Kind/Bug
Priority
Medium
3
Reviewed
Confirmed
1
labels 2026-08-19 05:25:38 +00:00
yonlu self-assigned this 2026-08-19 17:18:25 +00:00
yonlu added the
Status
In Progress
label 2026-08-19 17:18:25 +00:00
Owner

Making dev-headless.sh refuse to start when something already holds its port, naming the holder (pid + cwd) rather than silently handing the harness to it.

Making dev-headless.sh refuse to start when something already holds its port, naming the holder (pid + cwd) rather than silently handing the harness to it.
logan closed this issue 2026-08-19 19:08:46 +00:00
gitea-actions bot removed the
Status
In Progress
label 2026-08-19 19:09:11 +00:00
Sign in to join this conversation.
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: yonlu/yellowjacket#119