chore(telemetry): implement universal path normalizer and omni-reaper to prevent preview sprawl

This commit is contained in:
2026-06-09 16:11:31 -07:00
parent 7a9c2575f0
commit 137d5975e1
2 changed files with 39 additions and 22 deletions

View File

@@ -4568,18 +4568,26 @@ function normalizeFsPath(
);
}
// Prevent doubled subdirectory paths (e.g. getacquired-2-0/getacquired-2-0/)
if (p.startsWith("getacquired-2-0/")) {
p = p.substring("getacquired-2-0/".length);
// 1. Strip any leading /workspace/
let normalized = p.replace(/^\/workspace\//, "");
// 2. Strip any leading ./
if (normalized.startsWith("./")) {
normalized = normalized.slice(2);
}
// 3. Strip project slug prefix if the model hallucinates it (e.g. getacquired-2-0/src...)
if (projectSlug && normalized.startsWith(`${projectSlug}/`)) {
normalized = normalized.slice(projectSlug.length + 1);
}
// Handle any other legacy hardcoded occurrences
if (normalized.startsWith("getacquired-2-0/")) {
normalized = normalized.substring("getacquired-2-0/".length);
}
const projectRoot = FS_ROOT;
let abs: string;
if (p.startsWith("/")) {
abs = p;
} else {
abs = `${projectRoot}/${p}`.replace(/\/+/g, "/");
}
const abs = `${projectRoot}/${normalized}`.replace(/\/+/g, "/");
const norm = abs.replace(/\/[^/]+\/\.\.(?=\/|$)/g, "").replace(/\/+/g, "/");
// When projectSlug is set, REJECT paths outside the project root.
@@ -4588,7 +4596,7 @@ function normalizeFsPath(
return NextResponse.json(
{
ok: false,
error: `PATH_OUTSIDE_PROJECT: path "${p}" resolves to "${norm}" which is outside the active project at "${projectRoot}". Did you mean "${projectRoot}/${p.replace(/^\/+/, "")}"?`,
error: `PATH_OUTSIDE_PROJECT: path "${p}" resolves to "${norm}" which is outside the active project at "${projectRoot}".`,
},
{ status: 400 },
);

View File

@@ -827,31 +827,40 @@ export async function startDevServer(
throw new PortOutOfRangeError(opts.port);
}
// 2. Stop ALL tracked rows for this project on the target port.
// Previous runs may have crashed or exited without being marked
// stopped, causing stale rows to accumulate. We reap them
// unconditionally before starting anything new — the AI's intent
// is "I want THIS command on THIS port", so most-recent-write-wins.
const existingRows = await query<{ id: string; pid: number | null }>(
`SELECT id, pid FROM fs_dev_servers
WHERE project_id = $1 AND port = $2 AND state IN ('starting','running','failed')`,
[opts.projectId, opts.port],
// 2. Stop ALL tracked rows for this project on ALL preview ports.
// Because our socket reaper is infallible, the AI never needs to
// sprawl across multiple ports. We unconditionally reap and stop
// every active preview server for this project before starting a new one
// to keep the dashboard clean and prevent memory leaks.
const existingRows = await query<{
id: string;
pid: number | null;
port: number;
}>(
`SELECT id, pid, port FROM fs_dev_servers
WHERE project_id = $1 AND state IN ('starting','running','failed')`,
[opts.projectId],
);
const killPortNodeCmd =
`node -e '` +
`const fs = require("fs"); ` +
`const port = ${opts.port}; ` +
`const portsToKill = [${existingRows
.map((r) => r.port)
.concat(opts.port)
.join(",")}]; ` +
`try { ` +
`const hexPort = port.toString(16).toUpperCase().padStart(4, "0"); ` +
`const tcp = fs.readFileSync("/proc/net/tcp", "utf8"); ` +
`const inodes = []; ` +
`tcp.split("\\n").forEach(line => { ` +
`const parts = line.trim().split(/\\s+/); ` +
`if (parts.length > 9) { ` +
`const local = parts[1]; ` +
`for (const p of portsToKill) { ` +
`const hexPort = p.toString(16).toUpperCase().padStart(4, "0"); ` +
`if (local.endsWith(":" + hexPort)) { inodes.push(parts[9]); } ` +
`} ` +
`} ` +
`}); ` +
`if (inodes.length > 0) { ` +
`fs.readdirSync("/proc").forEach(file => { ` +
@@ -872,7 +881,7 @@ export async function startDevServer(
`}); ` +
`} ` +
`} catch (e) { ` +
`try { require("child_process").execSync("fuser -k -9 ${opts.port}/tcp 2>/dev/null || true"); } catch (err) {} ` +
`try { require("child_process").execSync("fuser -k -9 " + portsToKill.join(",") + "/tcp 2>/dev/null || true"); } catch (err) {} ` +
`}'`;
for (const row of existingRows) {