Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions packages/sdk/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,10 @@ export class AgentRelayClient {
};
}

get brokerPid(): number | undefined {
return this.child?.pid;
}

async start(): Promise<void> {
if (this.child) {
return;
Expand Down
3 changes: 3 additions & 0 deletions src/cli/commands/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@
onBrokerStderr?: (listener: (line: string) => void) => () => void;
/** Relaycast workspace API key, available after the hello handshake. */
workspaceKey?: string;
/** PID of the underlying broker process, when available. */
brokerPid?: number;
}

export interface CoreFileSystem {
Expand Down Expand Up @@ -164,7 +166,7 @@
}
}

function findDashboardBinaryDefault(fileSystem: CoreFileSystem): string | null {

Check warning on line 169 in src/cli/commands/core.ts

View workflow job for this annotation

GitHub Actions / lint

Function 'findDashboardBinaryDefault' has a complexity of 17. Maximum allowed is 15
// Allow explicit override via env var (for local development)
const envOverride = process.env.RELAY_DASHBOARD_BINARY;
if (envOverride && fileSystem.existsSync(envOverride)) {
Expand Down Expand Up @@ -259,6 +261,7 @@
shutdown: () => client.shutdown(),
onBrokerStderr: (listener: (line: string) => void) => client.onBrokerStderr(listener),
get workspaceKey() { return client.workspaceKey; },
get brokerPid() { return client.brokerPid; },
};
return relay;
}
Expand Down
25 changes: 21 additions & 4 deletions src/cli/lib/broker-lifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,9 +173,26 @@
async function killOrphanedBrokerProcesses(projectRoot: string, deps: CoreDependencies): Promise<void> {
try {
const shellQuote = (s: string): string => "'" + s.replace(/'/g, "'\\''") + "'";
const { stdout } = await deps.execCommand(
`ps aux | grep '[a]gent-relay-broker' | grep -F ${shellQuote(projectRoot)}`
);
const brokerName = path.basename(projectRoot) || 'project';
let stdout = '';
try {
const byName = await deps.execCommand(
`ps aux | grep '[a]gent-relay-broker' | grep -F ${shellQuote('--name ' + brokerName)}`
);
stdout = byName.stdout;
} catch {
// Name filter may not match older process invocations; try legacy path-based filter.
}
if (!stdout.trim()) {
try {
const byPath = await deps.execCommand(
`ps aux | grep '[a]gent-relay-broker' | grep -F ${shellQuote(projectRoot)}`
);
stdout = byPath.stdout;
} catch {
// Expected when no orphaned processes are matched by either strategy.
}
}
const lines = stdout.trim().split('\n').filter(Boolean);
for (const line of lines) {
const parts = line.trim().split(/\s+/);
Expand All @@ -198,7 +215,7 @@
}
}

function cleanupBrokerPidIfStopped(brokerPidPath: string, deps: CoreDependencies): void {

Check warning on line 218 in src/cli/lib/broker-lifecycle.ts

View workflow job for this annotation

GitHub Actions / lint

'cleanupBrokerPidIfStopped' is defined but never used. Allowed unused vars must match /^_/u
const pid = readPidFile(brokerPidPath, deps);
if (pid === null || !isProcessRunning(pid, deps)) {
safeUnlink(brokerPidPath, deps);
Expand Down Expand Up @@ -882,7 +899,7 @@
);
relay = started.relay;
apiPort = started.apiPort;
writeBrokerPid(brokerPidPath, deps.pid, deps);
writeBrokerPid(brokerPidPath, relay.brokerPid ?? deps.pid, deps);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Writing broker child PID instead of parent PID causes agent-relay down to orphan the parent CLI process and dashboard

The change from deps.pid to relay.brokerPid ?? deps.pid writes the broker child process PID to the PID file. When agent-relay down reads this PID and sends SIGTERM (src/cli/lib/broker-lifecycle.ts:1103), it kills the broker child directly. However, the parent agent-relay up process is still blocked on holdOpen() (src/cli/lib/broker-lifecycle.ts:997), which is a promise that never resolves (src/cli/commands/core.ts:339). The parent's SIGTERM/SIGINT signal handlers (src/cli/lib/broker-lifecycle.ts:976-995) are never triggered, so shutdownOnce() is never called, meaning the dashboard process (dashboardProcess) is never killed (src/cli/lib/broker-lifecycle.ts:701-706) and the parent process itself is never exited. This leaves both the parent CLI process and the dashboard as orphans after agent-relay down completes.

Affected scenario: background mode

In --background mode, the parent is a detached process (src/cli/lib/broker-lifecycle.ts:717-729). After agent-relay down kills only the broker grandchild, the detached parent becomes an invisible orphan that the user cannot easily discover or stop.

Suggested change
writeBrokerPid(brokerPidPath, relay.brokerPid ?? deps.pid, deps);
writeBrokerPid(brokerPidPath, deps.pid, deps);
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

const dashboardRelayUrl = resolveDashboardRelayUrl(apiPort, deps);
const expectedRelayUrl = getDefaultDashboardRelayUrl(apiPort);
if (
Expand Down
Loading