Skip to content
This repository was archived by the owner on Aug 6, 2026. It is now read-only.

Commit bdfa389

Browse files
fix(core): restore archived cloud task logs
Generated-By: PostHog Code Task-Id: c1bbe3cf-742b-4b24-bf96-d11a18b4cf22
1 parent 84ca9fa commit bdfa389

2 files changed

Lines changed: 71 additions & 1 deletion

File tree

packages/core/src/cloud-task/cloud-task-engine.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ class BackendStreamError extends Error {
8181

8282
interface TaskRunResponse {
8383
id: string;
84+
log_url?: string | null;
8485
status: TaskRunStatus;
8586
stage?: string | null;
8687
output?: Record<string, unknown> | null;
@@ -1106,7 +1107,10 @@ export class CloudTaskEngine extends TypedEventEmitter<CloudTaskEvents> {
11061107
}
11071108

11081109
if (isTerminalStatus(run.status)) {
1109-
const historicalEntries = await this.fetchAllSessionLogs(watcher);
1110+
let historicalEntries = await this.fetchAllSessionLogs(watcher);
1111+
if (historicalEntries?.length === 0 && run.log_url) {
1112+
historicalEntries = await this.fetchArchivedLogs(run.log_url);
1113+
}
11101114
const terminalWatcher = this.watchers.get(key);
11111115
if (!terminalWatcher || terminalWatcher !== watcher) return;
11121116
if (watcher.failed) return;
@@ -2154,6 +2158,24 @@ export class CloudTaskEngine extends TypedEventEmitter<CloudTaskEvents> {
21542158
}
21552159
}
21562160

2161+
private async fetchArchivedLogs(
2162+
logUrl: string,
2163+
): Promise<StoredLogEntry[] | null> {
2164+
try {
2165+
const response = await this.streamFetch(logUrl);
2166+
if (!response.ok) return null;
2167+
const content = await response.text();
2168+
if (!content.trim()) return [];
2169+
return content
2170+
.trim()
2171+
.split("\n")
2172+
.map((line) => JSON.parse(line) as StoredLogEntry);
2173+
} catch (error) {
2174+
this.log.warn("Cloud task archived logs fetch error", { logUrl, error });
2175+
return null;
2176+
}
2177+
}
2178+
21572179
private async resolveStreamTarget(watcher: WatcherState): Promise<void> {
21582180
const url = `${watcher.apiHost}/api/projects/${watcher.teamId}/tasks/${watcher.taskId}/runs/${watcher.runId}/stream_token/`;
21592181
try {

packages/core/src/cloud-task/cloud-task.test.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2532,6 +2532,54 @@ describe("CloudTaskEngine", () => {
25322532
expect(statusFetchCount).toBeLessThanOrEqual(2);
25332533
});
25342534

2535+
it("loads archived logs when a terminal run has no persisted session logs", async () => {
2536+
const updates: unknown[] = [];
2537+
service.on(CloudTaskEvent.Update, (payload) => updates.push(payload));
2538+
const archivedEntry = {
2539+
type: "notification",
2540+
timestamp: "2026-01-01T00:00:00Z",
2541+
};
2542+
2543+
mockNetFetch.mockImplementation((input: string | Request) => {
2544+
const url = typeof input === "string" ? input : input.url;
2545+
if (url.includes("/session_logs/")) {
2546+
return Promise.resolve(
2547+
createJsonResponse([], 200, { "X-Has-More": "false" }),
2548+
);
2549+
}
2550+
if (url === "https://logs.example.com/run-1.jsonl") {
2551+
return Promise.resolve(
2552+
new Response(`${JSON.stringify(archivedEntry)}\n`, { status: 200 }),
2553+
);
2554+
}
2555+
return Promise.resolve(
2556+
createJsonResponse({
2557+
id: "run-1",
2558+
status: "completed",
2559+
log_url: "https://logs.example.com/run-1.jsonl",
2560+
updated_at: "2026-01-01T00:00:00Z",
2561+
}),
2562+
);
2563+
});
2564+
2565+
service.watch({
2566+
taskId: "task-1",
2567+
runId: "run-1",
2568+
apiHost: "https://app.example.com",
2569+
teamId: 2,
2570+
});
2571+
2572+
await waitFor(() => updates.length === 1);
2573+
expect(updates[0]).toEqual(
2574+
expect.objectContaining({
2575+
kind: "snapshot",
2576+
newEntries: [archivedEntry],
2577+
totalEntryCount: 1,
2578+
status: "completed",
2579+
}),
2580+
);
2581+
});
2582+
25352583
const guardedFetchStatusExpectations = [
25362584
[
25372585
401,

0 commit comments

Comments
 (0)