From 3565712e444e018d6178127164afaa695a58c82b Mon Sep 17 00:00:00 2001 From: Brad Harris Date: Thu, 16 Jul 2026 22:00:05 -0600 Subject: [PATCH 1/3] Fix overflow E2E cleanup timeout --- e2e/overflow-layout.spec.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/e2e/overflow-layout.spec.ts b/e2e/overflow-layout.spec.ts index a79f0153..12097a49 100644 --- a/e2e/overflow-layout.spec.ts +++ b/e2e/overflow-layout.spec.ts @@ -90,7 +90,11 @@ async function seedOverflowAgents( Array.from({ length: count }, async (_, index) => { const agent = await createAgentViaAPI(request, { name: `e2e-agent-overflow-${Date.now()}-${index}`, - cwd: process.cwd(), + // These agents only provide enough rows to exercise sidebar overflow. + // Avoid Codex token harvesting and repo lifecycle hooks during bulk + // cleanup; neither behavior is relevant to this layout-only fixture. + type: "terminal", + cwd: "/tmp", }); await setAgentLatestEventViaAPI(request, agent.id, { type: "working", From abf1cf2f71694a294a7282aa6e117e5262851661 Mon Sep 17 00:00:00 2001 From: Brad Harris Date: Thu, 16 Jul 2026 22:15:13 -0600 Subject: [PATCH 2/3] Skip token harvesting for inert runtimes --- apps/server/src/agents/manager.ts | 5 +++ apps/server/test/db/agent-manager.test.ts | 44 +++++++++++++++++++++++ e2e/overflow-layout.spec.ts | 5 ++- 3 files changed, 51 insertions(+), 3 deletions(-) diff --git a/apps/server/src/agents/manager.ts b/apps/server/src/agents/manager.ts index 9226791c..b8b9f6a5 100644 --- a/apps/server/src/agents/manager.ts +++ b/apps/server/src/agents/manager.ts @@ -312,6 +312,11 @@ export class AgentManager { /** Harvest token usage for an agent, scoped to its CLI session if known. */ async harvestAgentTokens(agent: AgentRecord): Promise { + // Inert runtimes never launch CLI sessions, so there cannot be new token + // usage to collect. Skipping also keeps inert dev/test servers from + // scanning session history that belongs to the host environment. + if (!this.runtime.tracksSessions()) return; + await harvestTokenUsage( this.pool, { diff --git a/apps/server/test/db/agent-manager.test.ts b/apps/server/test/db/agent-manager.test.ts index 4f38711b..cda57374 100644 --- a/apps/server/test/db/agent-manager.test.ts +++ b/apps/server/test/db/agent-manager.test.ts @@ -1789,6 +1789,50 @@ describe("AgentManager", () => { await rm(tmpDir, { recursive: true, force: true }); }); + it("should skip harvesting only when the runtime is inert", async () => { + const { cwdToClaudeProjectDir } = + await import("../../src/agents/token-harvester.js"); + const projectDir = cwdToClaudeProjectDir(tmpDir); + await mkdir(projectDir, { recursive: true }); + + const inertManager = new AgentManager(pool, noopLogger, inertTestConfig); + const agent = await inertManager.createAgent({ + name: "inert-agent", + type: "claude", + cwd: tmpDir, + useWorktree: false, + }); + await writeFile( + path.join(projectDir, `${agent.cliSessionId}.jsonl`), + `${JSON.stringify({ + type: "assistant", + message: { + model: "claude-opus-4-6", + usage: { input_tokens: 500, output_tokens: 10 }, + }, + timestamp: "2026-04-01T10:00:00.000Z", + })}\n` + ); + + await inertManager.harvestAgentTokens(agent); + + const usage = await pool.query( + `SELECT COUNT(*)::int AS count FROM agent_token_usage WHERE agent_id = $1`, + [agent.id] + ); + expect(usage.rows[0].count).toBe(0); + + await manager.harvestAgentTokens(agent); + + const trackedRuntimeUsage = await pool.query( + `SELECT SUM(input_tokens)::int AS total FROM agent_token_usage WHERE agent_id = $1`, + [agent.id] + ); + expect(trackedRuntimeUsage.rows[0].total).toBe(500); + + await rm(projectDir, { recursive: true, force: true }); + }); + it("should harvest only the persona's session for a persona agent", async () => { const { cwdToClaudeProjectDir } = await import("../../src/agents/token-harvester.js"); diff --git a/e2e/overflow-layout.spec.ts b/e2e/overflow-layout.spec.ts index 12097a49..0f4d9b68 100644 --- a/e2e/overflow-layout.spec.ts +++ b/e2e/overflow-layout.spec.ts @@ -91,9 +91,8 @@ async function seedOverflowAgents( const agent = await createAgentViaAPI(request, { name: `e2e-agent-overflow-${Date.now()}-${index}`, // These agents only provide enough rows to exercise sidebar overflow. - // Avoid Codex token harvesting and repo lifecycle hooks during bulk - // cleanup; neither behavior is relevant to this layout-only fixture. - type: "terminal", + // Keep them outside the repo so bulk cleanup does not run unrelated + // repository lifecycle hooks for each synthetic agent. cwd: "/tmp", }); await setAgentLatestEventViaAPI(request, agent.id, { From 6aaedd1efb6514c6103696a31325b2ed2974288b Mon Sep 17 00:00:00 2001 From: Brad Harris Date: Thu, 16 Jul 2026 22:27:21 -0600 Subject: [PATCH 3/3] Isolate overflow E2E lifecycle hooks --- e2e/overflow-layout.spec.ts | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/e2e/overflow-layout.spec.ts b/e2e/overflow-layout.spec.ts index 0f4d9b68..3d9df08a 100644 --- a/e2e/overflow-layout.spec.ts +++ b/e2e/overflow-layout.spec.ts @@ -5,6 +5,9 @@ import { type Locator, type Page, } from "@playwright/test"; +import { mkdtemp, rm } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; import { cleanupE2EAgents, @@ -84,16 +87,17 @@ async function getWindowScrollY(page: Page): Promise { async function seedOverflowAgents( request: APIRequestContext, - count: number + count: number, + cwd: string ): Promise> { const created = await Promise.all( Array.from({ length: count }, async (_, index) => { const agent = await createAgentViaAPI(request, { name: `e2e-agent-overflow-${Date.now()}-${index}`, // These agents only provide enough rows to exercise sidebar overflow. - // Keep them outside the repo so bulk cleanup does not run unrelated - // repository lifecycle hooks for each synthetic agent. - cwd: "/tmp", + // Keep them in an owned directory outside the repo so bulk cleanup does + // not discover repository or shared temporary-directory lifecycle hooks. + cwd, }); await setAgentLatestEventViaAPI(request, agent.id, { type: "working", @@ -107,15 +111,22 @@ async function seedOverflowAgents( } test.describe("Overflow layout", () => { + let overflowCwd: string; + + test.beforeAll(async () => { + overflowCwd = await mkdtemp(join(tmpdir(), "dispatch-e2e-overflow-")); + }); + test.afterAll(async ({ request }) => { await cleanupE2EAgents(request); + await rm(overflowCwd, { recursive: true, force: true }); }); test("agents workspace keeps sidebar, media, and terminal overflow isolated", async ({ page, request, }) => { - const agents = await seedOverflowAgents(request, 24); + const agents = await seedOverflowAgents(request, 24, overflowCwd); const focusAgent = agents[0]!; await setAgentPinsViaDB(