Skip to content

Commit 4d81b90

Browse files
Fix overflow E2E cleanup timeout (#789)
* Fix overflow E2E cleanup timeout * Skip token harvesting for inert runtimes * Isolate overflow E2E lifecycle hooks
1 parent 4d2bede commit 4d81b90

3 files changed

Lines changed: 66 additions & 3 deletions

File tree

apps/server/src/agents/manager.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,11 @@ export class AgentManager {
312312

313313
/** Harvest token usage for an agent, scoped to its CLI session if known. */
314314
async harvestAgentTokens(agent: AgentRecord): Promise<void> {
315+
// Inert runtimes never launch CLI sessions, so there cannot be new token
316+
// usage to collect. Skipping also keeps inert dev/test servers from
317+
// scanning session history that belongs to the host environment.
318+
if (!this.runtime.tracksSessions()) return;
319+
315320
await harvestTokenUsage(
316321
this.pool,
317322
{

apps/server/test/db/agent-manager.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1794,6 +1794,50 @@ describe("AgentManager", () => {
17941794
await rm(tmpDir, { recursive: true, force: true });
17951795
});
17961796

1797+
it("should skip harvesting only when the runtime is inert", async () => {
1798+
const { cwdToClaudeProjectDir } =
1799+
await import("../../src/agents/token-harvester.js");
1800+
const projectDir = cwdToClaudeProjectDir(tmpDir);
1801+
await mkdir(projectDir, { recursive: true });
1802+
1803+
const inertManager = new AgentManager(pool, noopLogger, inertTestConfig);
1804+
const agent = await inertManager.createAgent({
1805+
name: "inert-agent",
1806+
type: "claude",
1807+
cwd: tmpDir,
1808+
useWorktree: false,
1809+
});
1810+
await writeFile(
1811+
path.join(projectDir, `${agent.cliSessionId}.jsonl`),
1812+
`${JSON.stringify({
1813+
type: "assistant",
1814+
message: {
1815+
model: "claude-opus-4-6",
1816+
usage: { input_tokens: 500, output_tokens: 10 },
1817+
},
1818+
timestamp: "2026-04-01T10:00:00.000Z",
1819+
})}\n`
1820+
);
1821+
1822+
await inertManager.harvestAgentTokens(agent);
1823+
1824+
const usage = await pool.query(
1825+
`SELECT COUNT(*)::int AS count FROM agent_token_usage WHERE agent_id = $1`,
1826+
[agent.id]
1827+
);
1828+
expect(usage.rows[0].count).toBe(0);
1829+
1830+
await manager.harvestAgentTokens(agent);
1831+
1832+
const trackedRuntimeUsage = await pool.query(
1833+
`SELECT SUM(input_tokens)::int AS total FROM agent_token_usage WHERE agent_id = $1`,
1834+
[agent.id]
1835+
);
1836+
expect(trackedRuntimeUsage.rows[0].total).toBe(500);
1837+
1838+
await rm(projectDir, { recursive: true, force: true });
1839+
});
1840+
17971841
it("should harvest only the persona's session for a persona agent", async () => {
17981842
const { cwdToClaudeProjectDir } =
17991843
await import("../../src/agents/token-harvester.js");

e2e/overflow-layout.spec.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import {
55
type Locator,
66
type Page,
77
} from "@playwright/test";
8+
import { mkdtemp, rm } from "node:fs/promises";
9+
import { tmpdir } from "node:os";
10+
import { join } from "node:path";
811

912
import {
1013
cleanupE2EAgents,
@@ -84,13 +87,17 @@ async function getWindowScrollY(page: Page): Promise<number> {
8487

8588
async function seedOverflowAgents(
8689
request: APIRequestContext,
87-
count: number
90+
count: number,
91+
cwd: string
8892
): Promise<Array<{ id: string; name: string }>> {
8993
const created = await Promise.all(
9094
Array.from({ length: count }, async (_, index) => {
9195
const agent = await createAgentViaAPI(request, {
9296
name: `e2e-agent-overflow-${Date.now()}-${index}`,
93-
cwd: process.cwd(),
97+
// These agents only provide enough rows to exercise sidebar overflow.
98+
// Keep them in an owned directory outside the repo so bulk cleanup does
99+
// not discover repository or shared temporary-directory lifecycle hooks.
100+
cwd,
94101
});
95102
await setAgentLatestEventViaAPI(request, agent.id, {
96103
type: "working",
@@ -104,15 +111,22 @@ async function seedOverflowAgents(
104111
}
105112

106113
test.describe("Overflow layout", () => {
114+
let overflowCwd: string;
115+
116+
test.beforeAll(async () => {
117+
overflowCwd = await mkdtemp(join(tmpdir(), "dispatch-e2e-overflow-"));
118+
});
119+
107120
test.afterAll(async ({ request }) => {
108121
await cleanupE2EAgents(request);
122+
await rm(overflowCwd, { recursive: true, force: true });
109123
});
110124

111125
test("agents workspace keeps sidebar, media, and terminal overflow isolated", async ({
112126
page,
113127
request,
114128
}) => {
115-
const agents = await seedOverflowAgents(request, 24);
129+
const agents = await seedOverflowAgents(request, 24, overflowCwd);
116130
const focusAgent = agents[0]!;
117131

118132
await setAgentPinsViaDB(

0 commit comments

Comments
 (0)