Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
878c863
Fix test broker leaks, state races, and signal-masked failures
apedroheringer Jul 22, 2026
96875ff
Fix broker ownership and lock reclamation
apedroheringer Jul 23, 2026
2f6cc6a
Reclaim stale sockets left by owned brokers
apedroheringer Jul 23, 2026
f51a8a4
Harden stale-socket reclaim after adversarial review
apedroheringer Jul 23, 2026
c0b5eb2
Fix tokenless legacy broker upgrades
apedroheringer Jul 25, 2026
64fdef1
Fix reused-PID lock ownership across platforms
apedroheringer Jul 25, 2026
c7f2cbe
Refactor private file and process helpers
apedroheringer Jul 25, 2026
75b6cda
Serialize broker teardown and session cleanup
apedroheringer Jul 25, 2026
47e48f8
Fix shutdown edge cases and consolidate duplicated helpers
apedroheringer Jul 25, 2026
7f69f4a
Keep Linux processes with unreadable /proc metadata running
apedroheringer Jul 25, 2026
f923bbf
Tighten remaining duplication in broker entry points
apedroheringer Jul 25, 2026
b5362a7
Verify legacy brokers by session artifacts, not launch cwd
apedroheringer Jul 25, 2026
422fe24
Honor verified legacy ownership during forced shutdown
apedroheringer Jul 25, 2026
958e7d3
Trust proven process ownership for endpoint cleanup
apedroheringer Jul 25, 2026
df3b460
Consolidate broker teardown and defer process identity lookup
apedroheringer Jul 25, 2026
dc263cb
Drop unused verifyProcess override from token matching
apedroheringer Jul 25, 2026
7186d79
Confirm broker spawn before persisting session state
apedroheringer Jul 25, 2026
ad3d852
Fail closed on pid file errors and unwind failed session persists
apedroheringer Jul 25, 2026
d990937
Pin broker liveness checks to a persisted process identity
apedroheringer Jul 25, 2026
f3204e8
Reclaim sessions whose dead leader left an orphaned process group
apedroheringer Jul 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion plugins/codex/hooks/hooks.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
{
"type": "command",
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/session-lifecycle-hook.mjs\" SessionEnd",
"timeout": 5
"timeout": 30
}
]
}
Expand Down
61 changes: 38 additions & 23 deletions plugins/codex/scripts/app-server-broker.mjs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
#!/usr/bin/env node

import fs from "node:fs";
import net from "node:net";
import path from "node:path";
import process from "node:process";

import { parseArgs } from "./lib/args.mjs";
import { BROKER_BUSY_RPC_CODE, CodexAppServerClient } from "./lib/app-server.mjs";
import { parseBrokerEndpoint } from "./lib/broker-endpoint.mjs";
import { ensurePrivateDir, removeFileIfExists, writePrivateFile } from "./lib/fs.mjs";

const STREAMING_METHODS = new Set(["turn/start", "review/start", "thread/compact/start"]);

Expand Down Expand Up @@ -41,28 +41,34 @@ function writePidFile(pidFile) {
if (!pidFile) {
return;
}
fs.mkdirSync(path.dirname(pidFile), { recursive: true });
fs.writeFileSync(pidFile, `${process.pid}\n`, "utf8");
ensurePrivateDir(path.dirname(pidFile));
writePrivateFile(pidFile, `${process.pid}\n`);
}

async function main() {
const [subcommand, ...argv] = process.argv.slice(2);
if (subcommand !== "serve") {
throw new Error("Usage: node scripts/app-server-broker.mjs serve --endpoint <value> [--cwd <path>] [--pid-file <path>]");
throw new Error(
"Usage: node scripts/app-server-broker.mjs serve --endpoint <value> --instance-token <value> [--cwd <path>] [--pid-file <path>]"
);
}

const { options } = parseArgs(argv, {
valueOptions: ["cwd", "pid-file", "endpoint"]
valueOptions: ["cwd", "pid-file", "endpoint", "instance-token"]
});

if (!options.endpoint) {
throw new Error("Missing required --endpoint.");
}
if (!options["instance-token"]) {
throw new Error("Missing required --instance-token.");
}

const cwd = options.cwd ? path.resolve(process.cwd(), options.cwd) : process.cwd();
const endpoint = String(options.endpoint);
const listenTarget = parseBrokerEndpoint(endpoint);
const pidFile = options["pid-file"] ? path.resolve(options["pid-file"]) : null;
const instanceToken = String(options["instance-token"]);
writePidFile(pidFile);

const appClient = await CodexAppServerClient.connect(cwd, { disableBroker: true });
Expand Down Expand Up @@ -99,18 +105,20 @@ async function main() {
}
}

async function shutdown(server) {
async function shutdown(server, responseSocket = null) {
for (const socket of sockets) {
socket.end();
if (socket === responseSocket) {
socket.destroySoon();
} else {
socket.destroy();
}
}
await appClient.close().catch(() => {});
await new Promise((resolve) => server.close(resolve));
if (listenTarget.kind === "unix" && fs.existsSync(listenTarget.path)) {
fs.unlinkSync(listenTarget.path);
}
if (pidFile && fs.existsSync(pidFile)) {
fs.unlinkSync(pidFile);
if (listenTarget.kind === "unix") {
removeFileIfExists(listenTarget.path);
}
removeFileIfExists(pidFile);
}

appClient.setNotificationHandler(routeNotification);
Expand Down Expand Up @@ -158,8 +166,18 @@ async function main() {
}

if (message.id !== undefined && message.method === "broker/shutdown") {
send(socket, { id: message.id, result: {} });
await shutdown(server);
if (message.params?.instanceToken !== instanceToken) {
send(socket, {
id: message.id,
error: buildJsonRpcError(-32003, "Broker shutdown identity did not match this instance.")
});
continue;
}
send(socket, {
id: message.id,
result: { pid: process.pid, instanceToken }
});
await shutdown(server, socket);
process.exit(0);
}

Expand Down Expand Up @@ -233,15 +251,12 @@ async function main() {
});
});

process.on("SIGTERM", async () => {
await shutdown(server);
process.exit(0);
});

process.on("SIGINT", async () => {
await shutdown(server);
process.exit(0);
});
for (const signal of ["SIGTERM", "SIGINT"]) {
process.on(signal, async () => {
await shutdown(server);
process.exit(0);
});
}

server.listen(listenTarget.path);
}
Expand Down
Loading