Skip to content

feat(server): a discovery file so the CLI can find the running server - #91

Merged
EdmondDantes merged 1 commit into
mainfrom
feat/server-discovery-file
Jul 27, 2026
Merged

feat(server): a discovery file so the CLI can find the running server#91
EdmondDantes merged 1 commit into
mainfrom
feat/server-discovery-file

Conversation

@EdmondDantes

Copy link
Copy Markdown
Contributor

What

The first slice of TODO item 2 (the CLI talks to the server, not to the database). Before the CLI can be made a thin client of the server, it needs to find a running server, and the workspace needs to stop two servers from writing the same project dbs at once.

  • New ServerLocator (src/ServerLocator.php): records {host, port, pid} to server.json at the workspace root, reads it back, and offers alive() — a TCP connect to the address.
  • Server::run() records the address after startup and drops the file on a clean stop only.
  • claw serve refuses to start when a live server for the same workspace already answers, with a message naming the address and pid.

Why this shape

  • One file at the workspace root, not one per project. A server owns every project db under a workspace, so a workspace has at most one server; the record is one file.
  • The file is a hint, never proof of liveness. A pid can be reused and a file can outlive a crash, so the truthful liveness test is whether the address still accepts a connection. A stale file left by a crash is discarded by the next serve's liveness check, and overwritten by the next successful start.
  • No /api/health probe. Discovery does not need one — for serve the liveness test is the TCP connect; for the future claw run cutover the real request is itself the liveness test.
  • The bind is the true mutex; the guard is a courtesy. Two concurrent serve on the same port collide on the OS bind. The guard catches the commoner case (a second serve while one already runs) with a clear message instead of a raw EADDRINUSE.

Failure modes handled

  • Crash leaves a stale filealive() fails, the record is ignored and replaced.
  • A process that fails to bind never became the server → it does not clear the record on the way out, so the loser of a bind race cannot erase the winner's discovery file (clear() is additionally owner-checked by pid).
  • Wildcard bind (serve --host 0.0.0.0) → recorded and probed as the loopback address a client can actually reach.

Not in this PR

Routing claw run through the server (POST /start + streaming the trace to the console) is the next slice. This one only lands the discovery file and the duplicate-guard.

Checks

composer qa green locally — cs, PHPStan level 8, and Testo (445 tests). New tests in tests/ServerLocatorTest.php cover the record round-trip, the missing/malformed/wrong-type records, the owner-checked clear, and the connect-based liveness.

One server per workspace writes its host/port/pid to server.json at the workspace root. serve refuses to start when a live server for the workspace already answers, so two processes cannot write the same project dbs. The file is a hint, never proof of liveness — a TCP connect is; a crash leaves a stale file that the next serve's liveness check discards. Foundation for routing 'claw run' through the server (TODO item 2).
@EdmondDantes
EdmondDantes merged commit b1f333f into main Jul 27, 2026
2 checks passed
@EdmondDantes
EdmondDantes deleted the feat/server-discovery-file branch July 27, 2026 18:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant