bind a free port, do not assert one

a plugin sidecar does not choose a port. it binds whatever is free and REPORTS
it, which is how every other sidecar here works and why none of them can collide.
av-club asserted 3020 because it started life as a standalone service.

that assumption cost a deploy the same day it became a plugin: fofos still had a
copy of the pre-plugin code running from ~/garbage/av-club, on `main`, holding
3020 — so the plugin's sidecar died on EADDRINUSE in a restart loop while the
platform reported "sidecar not available". two things claiming one number, and
neither able to say so.

AV_CLUB_PORT still pins it, for running outside Officer — the standalone mode
fofos/platform was written against, where the other side has to know where to
look. inside Officer nothing sets it.

the announcement now sends `server.port`, never the requested value. reporting
what you asked for rather than what you got is how a sidecar ends up advertising
a port it is not on.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
This commit is contained in:
2026-08-28 22:41:52 +01:00
co-authored by Claude Opus 5
parent 79ff366802
commit e2ed76eac4
2 changed files with 16 additions and 3 deletions
+14 -1
View File
@@ -22,7 +22,20 @@ function num(key: string, fallback: number, min: number): number {
return value;
}
export const PORT = num("AV_CLUB_PORT", 3020, 1);
/**
* The port to listen on. **0 means "any free one", and that is the default.**
*
* A plugin sidecar does not choose a number. It binds whatever is free and REPORTS it to the platform,
* which is how every other sidecar here works and why none of them can collide. av-club asserted 3020
* because it began life as a standalone service, and on 2026-08-28 that assumption cost a deploy: a
* stale copy of the pre-plugin code was still holding 3020 on fofos, so the plugin's sidecar died on
* EADDRINUSE in a restart loop.
*
* `AV_CLUB_PORT` still works and still pins the port. That is for running this outside Officer — the
* standalone mode fofos/platform was written against — where something on the other side has to know
* where to find it. Inside Officer nothing should set it.
*/
export const PORT = num("AV_CLUB_PORT", 0, 0);
/**
* Where assets live. Outside the repo by default, and deliberately not under any directory a deploy
+2 -2
View File
@@ -217,8 +217,8 @@ const connection = createSidecarConnector({
reply({ type: "error", id: (_cmd as { id: string }).id, error: "av-club takes no commands; use its HTTP API" });
},
onConnected() {
connection.send({ type: "av-club:server", port: PORT });
console.log(`[av-club] reported port ${PORT} to the API`);
connection.send({ type: "av-club:server", port: server.port! });
console.log(`[av-club] reported port ${server.port} to the API`);
},
});