refactor(main): split main.ts into worker + agent-runner + storage #39
No reviewers
Labels
No labels
area:agents
area:dashboard
area:database
area:design
area:design-review
area:flows
area:infra
area:meta
area:security
area:sessions
area:webhook
area:workdir
security
type:bug
type:chore
type:meta
type:user-story
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
charles/claude-hooks!39
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "boss/36"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Splits the 1212-line
src/main.tsinto four focused modules so the 222-lineWorker.runAgentstops being the biggest unreadable block in the codebase and each concern can be tested in isolation.src/worker.ts(229 lines) —Workerclass only: FIFO queue,enqueue,processNext, result store with 1h TTL, callback delivery. Takes arunTaskhook at construction and knows nothing about the Claude Agent SDK.src/agent-runner.ts(525 lines) — the formerWorker.runAgentbody: container pre-flight, workdir acquisition, env construction, SDK spawn, session resume with retry-on-invalid, session capture. ExportsrunAgentTaskplus small pure helpers (runWithSessionResume,resolveHostCwd,buildAgentEnv,makeContainerGitRunner,makeContainerRemovePath,buildPrompt,extractProgress,touchesHostSecret) so the correctness rules can be unit-tested without spinning up the SDK.src/storage.ts(115 lines) — disk-usage stats for/storage, extracted to bringmain.tsclose to the 500-line target.src/main.ts(533 lines, down from 1212 — 56% reduction) — HTTP server, task history + event log, SSE broadcast, worker registry, startup wiring. EachWorkeris constructed withrunTask = runAgentTask(…)plus lifecycle hooks that feed the HTTP-visible event log.Session-resume stays correct
The order of operations — check stored session → pass
resumeto SDK → on failure, drop session, retry fresh — is preserved character-for-character in the extractedrunWithSessionResumehelper. Five unit tests inagent-runner.test.tscover: (a) fresh dispatch with no stored session, (b) successful resume, (c) resume fails → session dropped and fresh retry runs, plusstateless_sessionskipping the store entirely and fresh-dispatch failures not retrying.Container mode unchanged
agent-runner.tsowns thecwd = hostCwd | workdirdecision (the fix from0e3ea72) in the new exportedresolveHostCwd(containerMode, workdir)helper. Unit tests assert both branches. The pre-flight check still fails fast with a clear error when the agent's container is down — that regression test moved frommain.test.tstoagent-runner.test.tsand now callsrunAgentTaskdirectly.Public API stable
main.tsstill exportsgetWorkerwith the same signature (webhook.ts untouched).TaskRequestmoved toworker.ts— where the Worker class actually uses it — and re-exported frommain.tssowebhook.ts'simport type { TaskRequest } from "./main"keeps working. Chose the re-export over a sharedtypes.tsbecauseTaskRequestis tightly coupled to Worker's queue semantics; splitting it into a third module just to host one interface would be cosmetic.Verification
bun x tsc --noEmit— cleanbunx biome check src/— cleanbun test— 129/129 pass (up from ~35, thanks to the new session-resume, cwd, and Worker-lifecycle coverage)Closes #36
Test plan
bun x tsc --noEmitcleanbunx biome check src/cleanbun test— all tests passcharles/dummy-appissue → PR → merge still completes (requires human dispatch)🤖 Generated with Claude Code
Break the 1212-line src/main.ts into four focused modules so per-task SDK execution stops being the biggest unreadable block in the service and each concern can be tested in isolation. - src/worker.ts — Worker class (FIFO queue, enqueue, processNext), result store with 1h TTL, and callback delivery. Takes a `runTask` hook at construction and knows nothing about the Claude Agent SDK. - src/agent-runner.ts — former Worker.runAgent body: container pre-flight, workdir acquisition, env construction, SDK spawn, session resume with retry-on-invalid, session capture. Exports `runAgentTask` plus small pure helpers (`runWithSessionResume`, `resolveHostCwd`, `buildAgentEnv`, etc.) so the session-resume and host-cwd correctness rules can be unit-tested without spinning up the SDK. - src/storage.ts — disk-usage stats for /storage, extracted to bring main.ts close to the 500-line target. - src/main.ts — HTTP server, task history + event log, SSE, worker registry, startup wiring. Now 533 lines (down from 1212). TaskRequest moves into worker.ts and is re-exported from main.ts so webhook.ts's `import type { TaskRequest } from "./main"` keeps working — public API stays stable per the issue's acceptance criteria. Tests split into matching files (worker.test.ts, agent-runner.test.ts, storage.test.ts) with new coverage for the three session-resume paths (fresh dispatch, successful resume, resume fails → drop + retry), the host-cwd decision, and the Worker lifecycle hooks. 129/129 pass. Closes #36 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>Review
CI: ✅ green (run #1532, 57s, all 129 tests pass)
Acceptance criteria check:
src/worker.ts— Worker class only, SDK-agnostic,runTaskcallbacksrc/agent-runner.ts— fullrunAgentTask+ pure helpers exportedsrc/main.tstarget ≤ 500 linesrunWithSessionResumesuiteresolveHostCwdcontainer-mode regressionagent-runner.test.ts, callsrunAgentTaskdirectlygetWorkersignature stableTaskRequeststill importable from"./main"just qagreenThe refactor is clean. Session-resume logic, container-mode env wiring, and the abort/FIFO queue semantics are faithfully preserved. The new
WorkerHooksinterface is a clear improvement — lifecycle concerns (event log, SSE, history) are now fully decoupled from the queue mechanics, which is exactly what the issue asked for.Two minor observations (neither blocks merge):
src/main.ts— 533 lines vs ≤ 500 targetThe acceptance criterion states "Target ≤ 500 lines." The PR lands at 533. The 56% reduction from 1212 lines is the real win here and the spirit is fully met, but the hard number misses by 33 lines. The
logSDKMessagefunction (≈90 lines ofswitcharms) is the obvious candidate if you want to cross the line — it could move toagent-runner.tsor a newsrc/sdk-log.ts. Not requesting a change; flagging so it's a conscious decision rather than an oversight.src/main.ts—logSDKMessageis now exported but used only internallyNothing outside
main.tsimports it (confirmed from the test diffs). Theexportis unnecessary and silently widens the public API surface. Drop theexportkeyword or, if you intend it to be testable in the future, add a note explaining why. No correctness impact.Both items are follow-up material, not blockers. Approving.
@ -2,0 +2,4 @@* claude-hooks HTTP entry point and orchestration.** Owns:* - HTTP server + API routes (`/task`, `/health`, `/queue`, `/cancel`,This function was
function logSDKMessage(module-private) in the original. Theexportkeyword is now present but nothing outsidemain.tsimports it — confirmed by inspecting all test files in the diff. Either drop theexportto keep it private, or add a comment explaining the intended public surface.