Skip to content

Commit 1bcf3c5

Browse files
committed
fix(agent): keep rtk cache rename on one filesystem and pin error-path emit ordering
- mkdtempSync beside cacheDir (not os.tmpdir()) so renameSync stays same-filesystem and never throws EXDEV on Linux/Docker containers - wrap renameSync in try/catch: swallow Windows destination-exists error when the winner already placed the file (concurrent cold-cache builds) - fix error message to interpolate extractedBinary (the path checked) not cachedBinary (the destination) - signalTaskComplete ordering test now passes a savings-returning stub so the rtk_savings enqueue is asserted to precede stop() on that seam - fix ENOBUFS comment -> ERR_CHILD_PROCESS_STDIO_MAXBUFFER (real error) - fix double space in emitRtkSavings comment Generated-By: PostHog Code Task-Id: b86391cc-4634-4a2b-ae34-fe1f9c0626a0
1 parent 20cb39c commit 1bcf3c5

4 files changed

Lines changed: 39 additions & 14 deletions

File tree

packages/agent/build/rtk-binary.mjs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -188,17 +188,27 @@ export async function ensureRtkBinary(destDir) {
188188
}
189189
// Extract into a temp dir first so a killed build cannot leave a partial
190190
// binary in cacheDir that existsSync accepts on the next run.
191-
const tmpDir = mkdtempSync(join(tmpdir(), "posthog-rtk-"));
191+
// Use dirname(cacheDir) so the temp dir and cacheDir are on the same
192+
// filesystem: rename(2) fails with EXDEV across filesystems (e.g. tmpfs /tmp
193+
// vs overlay node_modules/.cache in Docker / CI containers).
194+
const tmpDir = mkdtempSync(join(dirname(cacheDir), ".tmp-"));
192195
try {
193196
await extractArchive(archivePath, tmpDir, target);
194197
rmSync(archivePath, { force: true });
195198
const extractedBinary = join(tmpDir, binName);
196199
if (!existsSync(extractedBinary)) {
197200
throw new Error(
198-
`rtk binary missing after extraction: ${cachedBinary} — the release archive layout for ${target} may have changed`,
201+
`rtk binary missing after extraction: ${extractedBinary} — the release archive layout for ${target} may have changed`,
199202
);
200203
}
201-
renameSync(extractedBinary, cachedBinary);
204+
try {
205+
renameSync(extractedBinary, cachedBinary);
206+
} catch (renameError) {
207+
// Two concurrent cold-cache builds can race to the same destination.
208+
// On Windows renameSync throws when the destination already exists;
209+
// if the winner already wrote the file, the loser can proceed safely.
210+
if (!existsSync(cachedBinary)) throw renameError;
211+
}
202212
} finally {
203213
rmSync(tmpDir, { force: true, recursive: true });
204214
}

packages/agent/src/server/agent-server.test.ts

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -549,17 +549,24 @@ describe("AgentServer HTTP Mode", () => {
549549

550550
it("writes terminal failure status before completing event ingest", async () => {
551551
const order: string[] = [];
552-
const testServer = createServer() as unknown as {
552+
// Use a savings-returning resolver so the rtk_savings enqueue fires on this
553+
// path — the null default would skip emitRtkSavings and leave the ordering
554+
// of rtk enqueue < stop() untested on the signalTaskComplete seam.
555+
const testServer = createServer({
556+
resolveRtkSavings: async () => ({
557+
totalCommands: 1,
558+
inputTokens: 100,
559+
outputTokens: 50,
560+
tokensSaved: 30,
561+
avgSavingsPct: 30,
562+
}),
563+
}) as unknown as {
553564
eventStreamSender: {
554-
enqueue: (event: Record<string, unknown>) => void;
555-
stop: () => Promise<void>;
565+
enqueue: ReturnType<typeof vi.fn>;
566+
stop: ReturnType<typeof vi.fn>;
556567
};
557568
posthogAPI: {
558-
updateTaskRun: (
559-
taskId: string,
560-
runId: string,
561-
payload: Record<string, unknown>,
562-
) => Promise<unknown>;
569+
updateTaskRun: ReturnType<typeof vi.fn>;
563570
};
564571
signalTaskComplete(
565572
payload: JwtPayload,
@@ -595,7 +602,8 @@ describe("AgentServer HTTP Mode", () => {
595602
"boom",
596603
);
597604

598-
expect(order).toEqual(["enqueue", "update", "stop"]);
605+
// error enqueue -> status update -> rtk_savings enqueue -> stop
606+
expect(order).toEqual(["enqueue", "update", "enqueue", "stop"]);
599607
expect(testServer.eventStreamSender.enqueue).toHaveBeenCalledWith(
600608
expect.objectContaining({
601609
type: "notification",
@@ -613,6 +621,13 @@ describe("AgentServer HTTP Mode", () => {
613621
error_message: "boom",
614622
},
615623
);
624+
// Assert rtk_savings enqueue precedes stop() on this seam.
625+
const enqueueOrders =
626+
testServer.eventStreamSender.enqueue.mock.invocationCallOrder;
627+
const stopOrder =
628+
testServer.eventStreamSender.stop.mock.invocationCallOrder[0];
629+
// The last enqueue (rtk_savings) must land before stop()
630+
expect(enqueueOrders[enqueueOrders.length - 1]).toBeLessThan(stopOrder);
616631
});
617632

618633
it("still stops event ingest when terminal failure status update fails", async () => {

packages/agent/src/server/agent-server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3392,7 +3392,7 @@ ${signedCommitInstructions}
33923392
private async emitRtkSavings(): Promise<void> {
33933393
if (!this.eventStreamSender || this.rtkSavingsAttempted) return;
33943394
// Set before the await: a failed attempt still counts — prevents retry
3395-
// storms on the terminal path. The flag name reflects this intent.
3395+
// storms on the terminal path. The flag name reflects this intent.
33963396
this.rtkSavingsAttempted = true;
33973397

33983398
try {

packages/agent/src/server/rtk-savings.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ async function defaultRunGain(
5353
const { stdout } = await execFileAsync(binary, ["gain", "--format", "json"], {
5454
timeout: 5_000,
5555
// The daily array grows on long-lived hosts; cap the buffer explicitly so
56-
// ENOBUFS never silently swallows savings on desktop reuse.
56+
// ERR_CHILD_PROCESS_STDIO_MAXBUFFER never silently swallows savings on desktop reuse.
5757
maxBuffer: 10 * 1024 * 1024,
5858
env,
5959
});

0 commit comments

Comments
 (0)