Extract inline logic from BackgroundManager into focused modules#2034
Extract inline logic from BackgroundManager into focused modules#2034code-yeongyu merged 2 commits intodevfrom
Conversation
… focused modules Extract 5 concerns from BackgroundManager into dedicated modules: - error-classifier.ts: enhance with extractErrorName, extractErrorMessage, getSessionErrorMessage, isRecord - fallback-retry-handler.ts: standalone tryFallbackRetry with full retry logic - process-cleanup.ts: registerManagerForCleanup/unregisterManagerForCleanup - compaction-aware-message-resolver.ts: isCompactionAgent/findNearestMessageExcludingCompaction - Delete notification-builder.ts (duplicate of background-task-notification-template.ts) Manager.ts method bodies now delegate to extracted modules. Wire duration-formatter.ts and task-poller.ts (existing but unused). manager.ts: 2036 -> 1647 LOC (19% reduction). All 161 existing tests pass unchanged.
Add 104 new tests across 4 test files: - error-classifier.test.ts (80 tests): isRecord, isAbortedSessionError, getErrorText, extractErrorName, extractErrorMessage, getSessionErrorMessage - fallback-retry-handler.test.ts (19 tests): retry logic, fallback chain, concurrency release, session abort, queue management - process-cleanup.test.ts (7 tests): signal registration, multi-manager shutdown, cleanup on unregister - compaction-aware-message-resolver.test.ts (13 tests): compaction agent detection, message resolution with temp dirs (pre-existing, verified) Total background-agent tests: 161 -> 265 (104 new, 0 regressions)
There was a problem hiding this comment.
6 issues found across 10 files
Confidence score: 3/5
- Some risk here due to a guaranteed 6-second hang on Ctrl-C in
src/features/background-agent/process-cleanup.tswhen the exit timeout isn’t.unref()’d, which can stall shutdown behavior. - Tests in
src/features/background-agent/process-cleanup.test.tscall the SIGINT listener and triggerprocess.exit(), mutating global state and making cleanup tests unsafe or flaky across all three instances. - Additional test gaps in
src/features/background-agent/compaction-aware-message-resolver.test.tsmean invalid JSON and skip logic aren’t actually exercised, so regressions could slip through. - Pay close attention to
src/features/background-agent/process-cleanup.ts,src/features/background-agent/process-cleanup.test.ts,src/features/background-agent/compaction-aware-message-resolver.test.ts,src/features/background-agent/fallback-retry-handler.ts- shutdown hang, unsafe exit calls in tests, and retry task data loss.
Prompt for AI agents (all issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/features/background-agent/compaction-aware-message-resolver.test.ts">
<violation number="1" location="src/features/background-agent/compaction-aware-message-resolver.test.ts:99">
P2: This test does not actually verify the skipping logic. Because `002.json` is a valid match and evaluated first (due to reverse sorting), the compaction message (`001.json`) is never processed.</violation>
<violation number="2" location="src/features/background-agent/compaction-aware-message-resolver.test.ts:158">
P2: This test passes without evaluating the invalid JSON. Due to reverse sorting, `002.json` (valid) is evaluated and returned first, meaning `001.json` (invalid) is skipped entirely. Swap the filenames to fix.</violation>
</file>
<file name="src/features/background-agent/process-cleanup.test.ts">
<violation number="1" location="src/features/background-agent/process-cleanup.test.ts:67">
P1: Invoking the `SIGINT` listener triggers `process.exit()` and mutates the global exit code. Use the `exit` listener to test cleanup safely. (Note: Apply this fix to all 3 tests in this file where this pattern is used).</violation>
</file>
<file name="src/features/background-agent/compaction-aware-message-resolver.ts">
<violation number="1" location="src/features/background-agent/compaction-aware-message-resolver.ts:29">
P2: Optimize file reading by merging dual loops into a single pass to avoid redundant disk I/O.</violation>
</file>
<file name="src/features/background-agent/fallback-retry-handler.ts">
<violation number="1" location="src/features/background-agent/fallback-retry-handler.ts:119">
P2: The `retryInput` mapping omits the `isUnstableAgent` property from the `task`, causing fallback retries to lose their unstable agent status.</violation>
</file>
<file name="src/features/background-agent/process-cleanup.ts">
<violation number="1" location="src/features/background-agent/process-cleanup.ts:14">
P1: Missing `.unref()` on exit timeout causes a guaranteed 6-second hang on Ctrl-C.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
|
|
||
| registerManagerForCleanup(manager) | ||
|
|
||
| const [, listener] = processOnCalls[0] |
There was a problem hiding this comment.
P1: Invoking the SIGINT listener triggers process.exit() and mutates the global exit code. Use the exit listener to test cleanup safely. (Note: Apply this fix to all 3 tests in this file where this pattern is used).
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/features/background-agent/process-cleanup.test.ts, line 67:
<comment>Invoking the `SIGINT` listener triggers `process.exit()` and mutates the global exit code. Use the `exit` listener to test cleanup safely. (Note: Apply this fix to all 3 tests in this file where this pattern is used).</comment>
<file context>
@@ -0,0 +1,156 @@
+
+ registerManagerForCleanup(manager)
+
+ const [, listener] = processOnCalls[0]
+ listener()
+
</file context>
| handler() | ||
| if (exitAfter) { | ||
| process.exitCode = 0 | ||
| setTimeout(() => process.exit(), 6000) |
There was a problem hiding this comment.
P1: Missing .unref() on exit timeout causes a guaranteed 6-second hang on Ctrl-C.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/features/background-agent/process-cleanup.ts, line 14:
<comment>Missing `.unref()` on exit timeout causes a guaranteed 6-second hang on Ctrl-C.</comment>
<file context>
@@ -0,0 +1,81 @@
+ handler()
+ if (exitAfter) {
+ process.exitCode = 0
+ setTimeout(() => process.exit(), 6000)
+ }
+ }
</file context>
| agent: "oracle", | ||
| model: { providerID: "google", modelID: "gemini-2-flash" }, | ||
| } | ||
| writeFileSync(join(tempDir, "001.json"), invalidJson) |
There was a problem hiding this comment.
P2: This test passes without evaluating the invalid JSON. Due to reverse sorting, 002.json (valid) is evaluated and returned first, meaning 001.json (invalid) is skipped entirely. Swap the filenames to fix.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/features/background-agent/compaction-aware-message-resolver.test.ts, line 158:
<comment>This test passes without evaluating the invalid JSON. Due to reverse sorting, `002.json` (valid) is evaluated and returned first, meaning `001.json` (invalid) is skipped entirely. Swap the filenames to fix.</comment>
<file context>
@@ -0,0 +1,190 @@
+ agent: "oracle",
+ model: { providerID: "google", modelID: "gemini-2-flash" },
+ }
+ writeFileSync(join(tempDir, "001.json"), invalidJson)
+ writeFileSync(join(tempDir, "002.json"), JSON.stringify(validMessage))
+
</file context>
| agent: "sisyphus", | ||
| model: { providerID: "anthropic", modelID: "claude-opus-4-6" }, | ||
| } | ||
| writeFileSync(join(tempDir, "001.json"), JSON.stringify(compactionMessage)) |
There was a problem hiding this comment.
P2: This test does not actually verify the skipping logic. Because 002.json is a valid match and evaluated first (due to reverse sorting), the compaction message (001.json) is never processed.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/features/background-agent/compaction-aware-message-resolver.test.ts, line 99:
<comment>This test does not actually verify the skipping logic. Because `002.json` is a valid match and evaluated first (due to reverse sorting), the compaction message (`001.json`) is never processed.</comment>
<file context>
@@ -0,0 +1,190 @@
+ agent: "sisyphus",
+ model: { providerID: "anthropic", modelID: "claude-opus-4-6" },
+ }
+ writeFileSync(join(tempDir, "001.json"), JSON.stringify(compactionMessage))
+ writeFileSync(join(tempDir, "002.json"), JSON.stringify(validMessage))
+
</file context>
| .sort() | ||
| .reverse() | ||
|
|
||
| for (const file of files) { |
There was a problem hiding this comment.
P2: Optimize file reading by merging dual loops into a single pass to avoid redundant disk I/O.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/features/background-agent/compaction-aware-message-resolver.ts, line 29:
<comment>Optimize file reading by merging dual loops into a single pass to avoid redundant disk I/O.</comment>
<file context>
@@ -0,0 +1,57 @@
+ .sort()
+ .reverse()
+
+ for (const file of files) {
+ try {
+ const content = readFileSync(join(messageDir, file), "utf-8")
</file context>
| parentTools: task.parentTools, | ||
| model: task.model, | ||
| fallbackChain: task.fallbackChain, | ||
| category: task.category, |
There was a problem hiding this comment.
P2: The retryInput mapping omits the isUnstableAgent property from the task, causing fallback retries to lose their unstable agent status.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/features/background-agent/fallback-retry-handler.ts, line 119:
<comment>The `retryInput` mapping omits the `isUnstableAgent` property from the `task`, causing fallback retries to lose their unstable agent status.</comment>
<file context>
@@ -0,0 +1,125 @@
+ parentTools: task.parentTools,
+ model: task.model,
+ fallbackChain: task.fallbackChain,
+ category: task.category,
+ }
+ queue.push({ task, input: retryInput })
</file context>
Background
BackgroundManager(manager.ts) had grown to 2036 LOC with all logic inline — error classification, fallback retry, process cleanup, compaction-aware message resolution, and notification building were all embedded directly in the class. Thebackground-agent/directory already contained 15+ extracted modules from a previous incomplete refactoring attempt, but none were wired into the manager.This PR completes that extraction: manager.ts now delegates to focused modules while preserving identical behavior and public API.
Changes
Extracted modules (new files)
fallback-retry-handler.ts(125 LOC) — standalonetryFallbackRetry()with full retry/fallback chain logicprocess-cleanup.ts(82 LOC) —registerManagerForCleanup()/unregisterManagerForCleanup()for process signal handlingcompaction-aware-message-resolver.ts(57 LOC) —isCompactionAgent()/findNearestMessageExcludingCompaction()Enhanced modules
error-classifier.ts(21 → 83 LOC) — addedisRecord(),extractErrorName(),extractErrorMessage(),getSessionErrorMessage()Wired existing modules
duration-formatter.tsandtask-poller.ts— already existed but weren't imported by managerDeleted
notification-builder.ts— duplicate ofbackground-task-notification-template.tsManager.ts reduction
cleanupManagers,cleanupRegistered,cleanupHandlers)getErrorText,extractErrorName,extractErrorMessage,isRecord,getSessionErrorMessage,registerProcessSignal)New tests (104 tests across 4 files)
error-classifier.test.tsfallback-retry-handler.test.tsprocess-cleanup.test.tscompaction-aware-message-resolver.test.tsTesting
manager.test.tstests pass unchanged — no behavior changesReview Notes
index.tsbarrel exports remain identical (BackgroundManager,SubagentSessionCreatedEvent,OnSubagentSessionCreated)(manager as unknown as { tasks: ... })), all preservedprocess-cleanup.tsexports_resetForTesting()to handle module-level singleton state pollution across test files (whenmanager.test.tsruns beforeprocess-cleanup.test.ts)spawner.ts,state.ts,result-handler.ts,session-output-validator.ts) remain unwired — they diverged too much from manager's inline logic. Future PR scope.Summary by cubic
Extracted inline logic from BackgroundManager into focused modules while keeping behavior and the public API unchanged. Manager size reduced by 19%, and new tests cover the extracted logic.
Written for commit 8d66d56. Summary will update on new commits.