fix: prevent agents from killing production dashboard on port 4040
An AI review agent (FN-1506) killed the running dashboard by finding the process on port 4040 via lsof and running kill -9, causing exit code 137 (SIGKILL) with no logs. This adds multi-layer guardrails: - AGENTS.md: project-level rule reserving port 4040 - Executor/reviewer system prompts: explicit prohibition on killing port 4040 processes, with instruction to use --port 0 instead - Core agent-prompts.ts: same guardrails in all prompt variants - Reviewer told to issue REVISE if executor violates the rule - SIGHUP handlers in dashboard.ts and serve.ts for resilience - Background engine reconciliation in dashboard/serve startup Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
15
AGENTS.md
15
AGENTS.md
@@ -166,6 +166,21 @@ pnpm build # build all packages
|
||||
|
||||
Tests are required. Typechecks and manual verification are not substitutes for real tests with assertions.
|
||||
|
||||
## Port 4040 is reserved — never kill processes on it
|
||||
|
||||
Port 4040 is the production dashboard port. A user's live dashboard session is typically running there. **Agents must NEVER:**
|
||||
|
||||
- Run `kill`, `kill -9`, `pkill`, or `killall` against processes on port 4040
|
||||
- Run `lsof -ti:4040 | xargs kill` or any variant that kills the port holder
|
||||
- Start a test server on port 4040 — always use a random/ephemeral port (e.g., `--port 0` or `--port 9999`)
|
||||
|
||||
If port 4040 is in use when starting a test server, **pick a different port** — do not kill the existing process. The process on port 4040 is the user's live dashboard with active engine, agents, and state.
|
||||
|
||||
When testing dashboard endpoints in a worktree, use:
|
||||
```bash
|
||||
node dist/bin.js dashboard --dev --port 0 # OS assigns a random free port
|
||||
```
|
||||
|
||||
## Engine process rules
|
||||
|
||||
The engine (`packages/engine`) runs the executor, merger, scheduler, IPC host, and dashboard-facing activity loop on a single Node event loop. **Blocking that loop stalls every task concurrently in-flight.**
|
||||
|
||||
@@ -497,6 +497,14 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?:
|
||||
// Start engines for all registered projects eagerly
|
||||
await engineManager.startAll();
|
||||
|
||||
// Start background reconciliation to detect and start engines for projects
|
||||
// registered after startup (without requiring dashboard UI access).
|
||||
// This ensures project task execution starts from backend runtime alone.
|
||||
// The onProjectFirstAccessed callback in createServer remains as a fast-path
|
||||
// fallback for immediate engine startup on project access, but it is NOT
|
||||
// required for correctness — reconciliation handles all cases.
|
||||
engineManager.startReconciliation();
|
||||
|
||||
// Resolve the cwd project's engine for the dashboard's HTTP layer defaults.
|
||||
// The engine for the cwd project provides onMerge, automationStore, etc.
|
||||
// for requests that arrive without ?projectId=. This is transitional —
|
||||
@@ -572,6 +580,14 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?:
|
||||
};
|
||||
registerHandler(process, "SIGINT", () => void shutdown("SIGINT"));
|
||||
registerHandler(process, "SIGTERM", () => void shutdown("SIGTERM"));
|
||||
|
||||
// Ignore SIGHUP so the dashboard survives SSH session disconnects.
|
||||
// Without this, SIGHUP (sent when the controlling terminal closes) kills
|
||||
// the process silently — the exit handler tries to log to the now-dead
|
||||
// PTY and the write is lost.
|
||||
registerHandler(process, "SIGHUP", () => {
|
||||
console.log("[dashboard] Received SIGHUP (terminal disconnected) — ignoring");
|
||||
});
|
||||
} else {
|
||||
// Dev mode: create HeartbeatMonitor + TriggerScheduler inline (engine not started)
|
||||
try {
|
||||
@@ -698,6 +714,11 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?:
|
||||
};
|
||||
registerHandler(process, "SIGINT", () => void devShutdown("SIGINT"));
|
||||
registerHandler(process, "SIGTERM", () => void devShutdown("SIGTERM"));
|
||||
|
||||
// Ignore SIGHUP so the dashboard survives SSH session disconnects
|
||||
registerHandler(process, "SIGHUP", () => {
|
||||
console.log("[dashboard] Received SIGHUP (terminal disconnected) — ignoring");
|
||||
});
|
||||
}
|
||||
|
||||
const server = app.listen(selectedPort);
|
||||
|
||||
@@ -302,6 +302,14 @@ export async function runServe(
|
||||
// Start engines for all registered projects eagerly
|
||||
await engineManager.startAll();
|
||||
|
||||
// Start background reconciliation to detect and start engines for projects
|
||||
// registered after startup (without requiring headless node API access).
|
||||
// This ensures project task execution starts from backend runtime alone.
|
||||
// The onProjectFirstAccessed callback in createServer remains as a fast-path
|
||||
// fallback for immediate engine startup on project access, but it is NOT
|
||||
// required for correctness — reconciliation handles all cases.
|
||||
engineManager.startReconciliation();
|
||||
|
||||
// Get the cwd project's engine and store for the HTTP layer.
|
||||
// serve.ts needs a store for plugin setup, diagnostics, and the server.
|
||||
const cwdEngine = ntfyProjectId ? engineManager.getEngine(ntfyProjectId) : undefined;
|
||||
@@ -618,4 +626,12 @@ export async function runServe(
|
||||
process.on("SIGTERM", () => {
|
||||
void shutdown();
|
||||
});
|
||||
|
||||
// Ignore SIGHUP so the server survives SSH session disconnects.
|
||||
// Without this, SIGHUP (sent when the controlling terminal closes) kills
|
||||
// the process silently — the exit handler tries to log to the now-dead
|
||||
// PTY and the write is lost.
|
||||
process.on("SIGHUP", () => {
|
||||
console.log("[serve] Received SIGHUP (terminal disconnected) — ignoring");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -115,6 +115,7 @@ You are running in an **isolated git worktree**. This means:
|
||||
If you attempt to write to a path outside the worktree, the file tools will reject the operation with an error explaining the boundary.
|
||||
|
||||
## Guardrails
|
||||
- **NEVER kill processes on port 4040.** Port 4040 is the production dashboard. Do not run \`kill\`, \`pkill\`, \`killall\`, or \`lsof -ti:4040 | xargs kill\` against it. If you need to start a test server, use \`--port 0\` for a random free port. If port 4040 is occupied, pick a different port — do NOT kill the occupant.
|
||||
- Treat the File Scope in PROMPT.md as the expected starting scope, not a hard boundary when quality gates fail
|
||||
- Read "Context to Read First" files before starting
|
||||
- Follow the "Do NOT" section strictly
|
||||
@@ -400,7 +401,10 @@ access to the codebase and can run commands to inspect code.
|
||||
|
||||
### Suggestions
|
||||
- [Optional improvements, not blocking]
|
||||
\`\`\``;
|
||||
\`\`\`
|
||||
|
||||
## Safety Rules
|
||||
- **NEVER kill processes on port 4040.** Port 4040 is the production dashboard. If you need to test server endpoints, start a server on a different port (\`--port 0\` for random). If port 4040 is occupied, use a different port — do NOT kill the occupant. Issue REVISE if the executor kills or attempts to kill processes on port 4040.`;
|
||||
|
||||
/**
|
||||
* Base merger prompt text (without commit format instructions, which are
|
||||
@@ -522,6 +526,7 @@ You are running in an **isolated git worktree**. This means:
|
||||
If you attempt to write to a path outside the worktree, the file tools will reject the operation with an error explaining the boundary.
|
||||
|
||||
## Guardrails
|
||||
- **NEVER kill processes on port 4040.** Port 4040 is the production dashboard. Do not run \`kill\`, \`pkill\`, \`killall\`, or \`lsof -ti:4040 | xargs kill\` against it. If you need to start a test server, use \`--port 0\` for a random free port. If port 4040 is occupied, pick a different port — do NOT kill the occupant.
|
||||
- Treat the File Scope in PROMPT.md as the expected starting scope, not a hard boundary when quality gates fail
|
||||
- Read "Context to Read First" files before starting
|
||||
- Follow the "Do NOT" section strictly
|
||||
@@ -675,7 +680,10 @@ submissions to a high bar for correctness, security, and maintainability.
|
||||
|
||||
### Suggestions
|
||||
- [Optional improvements, not blocking]
|
||||
\`\`\``;
|
||||
\`\`\`
|
||||
|
||||
## Safety Rules
|
||||
- **NEVER kill processes on port 4040.** Port 4040 is the production dashboard. If you need to test server endpoints, start a server on a different port (\`--port 0\` for random). If port 4040 is occupied, use a different port — do NOT kill the occupant. Issue REVISE if the executor kills or attempts to kill processes on port 4040.`;
|
||||
|
||||
const CONCISE_TRIAGE_PROMPT_TEXT = `You are a task specification agent for "fn". Produce a concise, actionable PROMPT.md from the given task description.
|
||||
|
||||
|
||||
@@ -229,6 +229,7 @@ You are running in an **isolated git worktree**. This means:
|
||||
If you attempt to write to a path outside the worktree, the file tools will reject the operation with an error explaining the boundary.
|
||||
|
||||
## Guardrails
|
||||
- **NEVER kill processes on port 4040.** Port 4040 is the production dashboard. Do not run \`kill\`, \`pkill\`, \`killall\`, or \`lsof -ti:4040 | xargs kill\` against it. If you need to start a test server, use \`--port 0\` for a random free port. If port 4040 is occupied, pick a different port — do NOT kill the occupant.
|
||||
- Treat the File Scope in PROMPT.md as the expected starting scope, not a hard boundary when quality gates fail
|
||||
- Read "Context to Read First" files before starting
|
||||
- Follow the "Do NOT" section strictly
|
||||
|
||||
@@ -172,6 +172,7 @@ the changes into the assigned worktree.
|
||||
- Be constructive — suggest fixes, not just problems
|
||||
- Be proportional — don't block on style nits
|
||||
- Output your review as plain text (not to a file)
|
||||
- **NEVER kill processes on port 4040.** Port 4040 is the production dashboard. If you need to test server endpoints, start a server on a different port (\`--port 0\` for random). If port 4040 is occupied, use a different port — do NOT kill the occupant. Issue REVISE if the executor kills or attempts to kill processes on port 4040.
|
||||
`;
|
||||
|
||||
export type ReviewType = "plan" | "code" | "spec";
|
||||
|
||||
Reference in New Issue
Block a user