fix(atlas): restore agent mismatch guard for subagent boulder continuation (#18681)

This commit is contained in:
Sami Jawhar
2026-03-23 00:57:24 +00:00
parent 30dc50d880
commit 5777bf9894
3 changed files with 83 additions and 2 deletions
+63 -2
View File
@@ -6,7 +6,7 @@ import { tmpdir } from "node:os"
import { join } from "node:path"
import { clearBoulderState, readBoulderState, writeBoulderState } from "../../features/boulder-state"
import type { BoulderState } from "../../features/boulder-state"
import { _resetForTesting, subagentSessions } from "../../features/claude-code-session-state"
import { _resetForTesting, setSessionAgent, subagentSessions } from "../../features/claude-code-session-state"
const { createAtlasHook } = await import("./index")
@@ -16,7 +16,7 @@ describe("atlas hook idle-event session lineage", () => {
let testDirectory = ""
let promptCalls: Array<unknown> = []
function writeIncompleteBoulder(): void {
function writeIncompleteBoulder(overrides: Partial<BoulderState> = {}): void {
const planPath = join(testDirectory, "test-plan.md")
writeFileSync(planPath, "# Plan\n- [ ] Task 1\n- [ ] Task 2")
@@ -25,6 +25,7 @@ describe("atlas hook idle-event session lineage", () => {
started_at: "2026-01-02T10:00:00Z",
session_ids: [MAIN_SESSION_ID],
plan_name: "test-plan",
...overrides,
}
writeBoulderState(testDirectory, state)
@@ -103,6 +104,7 @@ describe("atlas hook idle-event session lineage", () => {
writeIncompleteBoulder()
subagentSessions.add(subagentSessionID)
setSessionAgent(subagentSessionID, "atlas")
const hook = createHook({
[subagentSessionID]: intermediateParentSessionID,
@@ -119,4 +121,63 @@ describe("atlas hook idle-event session lineage", () => {
assert.equal(readBoulderState(testDirectory)?.session_ids.includes(subagentSessionID), true)
assert.equal(promptCalls.length, 1)
})
it("does not inject continuation for boulder-lineage subagent with non-matching agent", async () => {
const subagentSessionID = "subagent-session-agent-mismatch"
writeIncompleteBoulder({ agent: "atlas" })
subagentSessions.add(subagentSessionID)
setSessionAgent(subagentSessionID, "sisyphus-junior")
const hook = createHook({
[subagentSessionID]: MAIN_SESSION_ID,
})
await hook.handler({
event: {
type: "session.idle",
properties: { sessionID: subagentSessionID },
},
})
assert.equal(readBoulderState(testDirectory)?.session_ids.includes(subagentSessionID), true)
assert.equal(promptCalls.length, 0)
})
it("injects continuation for boulder-lineage subagent with matching agent", async () => {
const subagentSessionID = "subagent-session-agent-match"
writeIncompleteBoulder({ agent: "atlas" })
subagentSessions.add(subagentSessionID)
setSessionAgent(subagentSessionID, "atlas")
const hook = createHook({
[subagentSessionID]: MAIN_SESSION_ID,
})
await hook.handler({
event: {
type: "session.idle",
properties: { sessionID: subagentSessionID },
},
})
assert.equal(promptCalls.length, 1)
})
it("injects continuation for explicitly tracked boulder session regardless of agent", async () => {
writeIncompleteBoulder({ agent: "atlas" })
setSessionAgent(MAIN_SESSION_ID, "hephaestus")
const hook = createHook()
await hook.handler({
event: {
type: "session.idle",
properties: { sessionID: MAIN_SESSION_ID },
},
})
assert.equal(promptCalls.length, 1)
})
})
+19
View File
@@ -5,6 +5,8 @@ import {
readBoulderState,
readCurrentTopLevelTask,
} from "../../features/boulder-state"
import { getSessionAgent, subagentSessions } from "../../features/claude-code-session-state"
import { getAgentConfigKey } from "../../shared/agent-display-names"
import { log } from "../../shared/logger"
import { injectBoulderContinuation } from "./boulder-continuation-injector"
import { HOOK_NAME } from "./hook-name"
@@ -136,6 +138,23 @@ export async function handleAtlasSessionIdle(input: {
})
}
if (subagentSessions.has(sessionID)) {
const sessionAgent = getSessionAgent(sessionID)
const agentKey = getAgentConfigKey(sessionAgent ?? "")
const requiredAgentKey = getAgentConfigKey(boulderState.agent ?? "atlas")
const agentMatches =
agentKey === requiredAgentKey ||
(requiredAgentKey === getAgentConfigKey("atlas") && agentKey === getAgentConfigKey("sisyphus"))
if (!agentMatches) {
log(`[${HOOK_NAME}] Skipped: subagent agent does not match boulder agent`, {
sessionID,
agent: sessionAgent ?? "unknown",
requiredAgent: boulderState.agent ?? "atlas",
})
return
}
}
const sessionState = getState(sessionID)
const now = Date.now()
+1
View File
@@ -1282,6 +1282,7 @@ session_id: ses_untrusted_999
}
writeBoulderState(TEST_DIR, state)
subagentSessions.add(subagentSessionID)
updateSessionAgent(subagentSessionID, "atlas")
const mockInput = createMockPluginInput()
const hook = createAtlasHook(mockInput)