refactor: split main.ts god module into focused sub-modules #1145
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/agent-hooks!1145
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "code-lead/1137"
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
Closes #1137.
main.tswas 4924 lines, owning HTTP routing, ~50 route handler bodies, the YAML engine wiring, startup orchestration, the SPA static serve, and the graceful-shutdown handler. This change decomposes those into focused sub-modules so each concern can be read, tested, and changed independently — and getsmain.tsto a 41-line orchestrator (under the issue's ≤300 LOC target).Decomposition
handleRequestapps/server/src/http/server.tsBun.serve, signal handlers)apps/server/src/setup/bootstrap.ts(startServer())apps/server/src/domain/flows-yaml/engine-bootstrap.ts/repos+ agents CRUD + provider state + models +/agents/healthhttp/handlers/agents.tshttp/handlers/tasks.ts/health+/queue+/events+/architect/stream+/storage+/sweep+/reset+/settingshttp/handlers/system.ts/history+/usage+/tasks/:id/artifactshttp/handlers/history.tshttp/handlers/web-app.tsmarkRunningAsInterrupted+ loginfrastructure/recovery/scan-interrupted.tsclaudeCodeVersioncache (read by/health)infrastructure/runtime/version.tsThe
WorkerRegistry,SSEBroadcaster, and task-history append/query listed in the AC were already extracted in earlier passes (domain/dispatch/registry.ts,http/sse.ts,infrastructure/database/task-store.ts+infrastructure/event-log.ts); they're wired through the new layout as-is.Stable public surface
main.tsre-exportshandleRequest,CANCEL_UNKNOWN_USER,resetAgentsHealthCache,scanInterruptedAtBoot, and theTaskRequest/TaskResult/WorkerConfigtypes so existing test imports (main.test.ts,main-agents.test.ts,session-gate.test.ts,history-contract.test.ts,agents-health.test.ts) and thewebhook.ts/post-ci.tsimport type { TaskRequest } from "./main"consumers keep resolving without churn.Constraints honoured
/architect/transcribe/healthregistration noted inhttp/server.ts, kept to avoid behavioural drift).just dev/just startentrypoints:bun run apps/server/src/main.tsis still the process entry; theimport.meta.mainblock callsstartServer().Out of scope (per issue)
webhook-config.tsboard.tsTest plan
just qagreen: 3332 tests pass, biome lint+format clean, typecheck green across all four workspace packages, sql-layer-check / paraglide-check / i18n-string-check / flow-schema-check pass.main.tsis 41 lines (well under the 300 LOC AC).systemctl --user restart claude-hooksshows the same startup logs (container reconcile, sweepers, watchdog)./,/api/me,/api/whoami,/api/agents)./eventsstays open and emits heartbeats./historyround-trip.🤖 Generated with Claude Code
main.ts was 4924 lines, owning HTTP routing, route handlers, the YAML engine wiring, startup orchestration, the SPA static serve, and the graceful-shutdown handler. Decompose into focused modules so each concern can be read, tested, and changed independently. Per the issue's AC, the in-scope concerns now live as follows: - HTTP routing (Hono app + every route + handleRequest) → apps/server/src/http/server.ts - Boot-time wiring (crypto, DB, container reconcile, sweepers, watchdog, Bun.serve, signal handlers) → apps/server/src/setup/bootstrap.ts - YAML engine wiring (live caps + dispatcher lifecycle + roots / registry ownership) → apps/server/src/domain/flows-yaml/engine-bootstrap.ts - HTTP handler bodies, split by domain: • http/handlers/agents.ts (repos + agent CRUD + provider state + models + /agents/health) • http/handlers/tasks.ts (submit/get/steer/pause/resume/fork/redispatch /cancel/approvals/breakdown) • http/handlers/system.ts (health/queue/events/architect-stream/ storage/sweep/reset/settings) • http/handlers/history.ts (history/usage/artifacts) • http/handlers/web-app.ts (SPA + manifest + icons) - Boot-time recovery (markRunningAsInterrupted + log) → infrastructure/recovery/scan-interrupted.ts - claude_code_version cache (read by /health) → infrastructure/runtime/version.ts The WorkerRegistry, SSEBroadcaster, and task-history append/query listed in the AC were already extracted in earlier passes (`domain/dispatch/registry.ts`, `http/sse.ts`, `infrastructure/database/task-store.ts` + `infrastructure/event-log.ts`); they're wired through the new layout as-is. main.ts is now 41 lines — imports `startServer` from setup/bootstrap.ts, re-exports `handleRequest`, `CANCEL_UNKNOWN_USER`, `resetAgentsHealthCache`, `scanInterruptedAtBoot`, and the `TaskRequest` / `TaskResult` / `WorkerConfig` types so existing test imports (`main.test.ts`, `main-agents.test.ts`, `session-gate.test.ts`, `history-contract.test.ts`, `agents-health.test.ts`, `webhook.ts`, `post-ci.ts`) keep resolving. Verification: - `just qa` green: 3332 tests pass, biome lint+format clean, typecheck green across all four workspace packages, sql-layer-check passes, paraglide-check passes, flow-schema-check passes. - No change to public HTTP API: every route path + verb is preserved verbatim (including the `/architect/transcribe/health` shadow- registration noted in `http/server.ts`). - No change to systemd unit or `just dev` / `just start` entrypoints — `bun run apps/server/src/main.ts` is still the process entry; the `import.meta.main` block calls `startServer()`. Out of scope per the issue: splitting webhook-config.ts, splitting board.ts, and the Drizzle ORM migration — all tracked separately. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>616b13f5f150b3c9c801Code Review — PR #1145: split
main.tsgod moduleCI: ✅ green | Mergeable: ✅ yes
Overview
This is a clean, well-executed decomposition. All 4 924 lines of the original
main.tsare fully accounted for across the 10 new modules — nothing was dropped and nothing was duplicated except for the two intentionally-preserved legacy route registrations. The resultingmain.tsis 41 lines (well within the ≤300 LOC AC). The public API surface (handleRequest,CANCEL_UNKNOWN_USER,resetAgentsHealthCache,scanInterruptedAtBoot,TaskRequest,TaskResult,WorkerConfig) is preserved verbatim, so all existing importers and tests are unaffected without any test-file churn.Path arithmetic in every new module was verified:
web-app.ts→ 4×..fromhttp/handlers/→apps/→apps/web/dist✅engine-bootstrap.ts→ 5×..to workspace root ✅setup/bootstrap.ts→ 5×..toDockerfile(adjusted from 4×..in oldmain.tsdue to deeper nesting) ✅Module graph is acyclic; singleton state is confined to the correct layers.
Issues
🟡 Minor —
runSweepimport +void runSweepinsetup/bootstrap.tsbootstrap.tsimports{ runSweep, startSweeper }frombackground/sweeper, usesstartSweeper(…)correctly, but then endsstartServer()with:runSweepis already consumed legitimately inhttp/handlers/system.tsfor the/sweeproute. The import inbootstrap.tsis unnecessary and thevoid Xtrick is a code smell. Remove both the import and the expression frombootstrap.ts.🟡 Minor — dead
bootstrapFlowsYamlEnginere-export fromhttp/server.tshttp/server.tsre-exportsbootstrapFlowsYamlEnginewith the comment "so the boot path can call it after route registration." In practice,setup/bootstrap.tsimportsbootstrapFlowsYamlEnginedirectly fromdomain/flows-yaml/engine-bootstrap, not viahttp/server.ts. No file imports it throughserver.ts. This re-export is unreachable dead code — remove it.🔵 Known / documented — duplicate route registrations in
http/server.tsTwo routes survive as double-registrations:
GET /api/watched-repos— the non-guarded version (returning rawwebhook_id/has_secretfields) is shadowed by the guardedhandleWatchedReposListvariant beneath it. Hono picks the last match, so behaviour is correct.GET /architect/transcribe/health— sync variant shadowed by async variant.Both are explicitly annotated with "Kept verbatim to avoid behavioural drift; can be deduplicated in a follow-up." Not a regression, and the annotation is accurate. Fine to leave for now.
Summary
The two minor issues (spurious
void runSweep+ dead re-export) are lint smells, not correctness problems. Everything else is solid. Approving.