Skip to content
Open
22 changes: 15 additions & 7 deletions apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12102,7 +12102,7 @@ describe("ClaudeAdapterV2 background wake turns", () => {
);

it.effect(
"uses task_started as an incremental roster fallback, keeps its tool call, and clears internal work on empty snapshot",
"uses task_started as an incremental roster fallback and keeps its owed wake after an empty snapshot",
() =>
Effect.gen(function* () {
const harness = yield* makeWakeHarness;
Expand Down Expand Up @@ -12250,7 +12250,7 @@ describe("ClaudeAdapterV2 background wake turns", () => {
).map((task) => task.taskId),
WAKE_TASK_ID,
);
assert.isFalse(yield* harness.hasPendingBackgroundWork);
assert.isTrue(yield* harness.hasPendingBackgroundWork);
}).pipe(
Effect.provide(
Layer.mergeAll(
Expand Down Expand Up @@ -15308,9 +15308,18 @@ describe("ClaudeAdapterV2 background wake turns", () => {
assert.isTrue(yield* harness.hasPendingBackgroundWork);
assert.lengthOf(harness.continuationRequests, 0);

yield* harness.offerAndWait(
claudeSdkFrame({
type: "system",
subtype: "background_tasks_changed",
tasks: [],
uuid: "roster-before-notification",
}),
);
assert.isTrue(yield* harness.hasPendingBackgroundWork);
yield* harness.offerAndWait(wakeNotification);
assert.lengthOf(harness.continuationRequests, 0);
assert.isFalse(yield* harness.hasPendingBackgroundWork);
assert.isTrue(yield* harness.hasPendingBackgroundWork);

yield* Queue.offer(harness.sdkMessages, wakeAssistant);
yield* harness.awaitContinuation(1);
Expand Down Expand Up @@ -16199,8 +16208,7 @@ describe("ClaudeAdapterV2 background wake turns", () => {
yield* harness.offerAndWait(emptyRoster);
yield* harness.offerAndWait(wakeNotification);
if (emptyLevel === "after") yield* harness.offerAndWait(emptyRoster);
// Query replacement reads the internal roster, which the exit already cleared.
assert.isFalse(yield* harness.hasPendingBackgroundWork);
assert.isTrue(yield* harness.hasPendingBackgroundWork);
yield* harness.offerAndWait(wakeTurnInit);
yield* harness.runtime.startTurn(
makeClaudeTestTurnInput({
Expand Down Expand Up @@ -20662,7 +20670,7 @@ describe("ClaudeAdapterV2 background wake turns", () => {
);
yield* Queue.offer(firstProcess, turnOneResult);
yield* changes.until(() => events.some((event) => event.type === "turn.terminal"));
assert.isFalse(yield* hasPendingBackgroundWork);
assert.isTrue(yield* hasPendingBackgroundWork);

const alternateModel = CLAUDE_TEST_RESTART_MODEL_SELECTION;
const activeProviderThread = {
Expand Down Expand Up @@ -20861,7 +20869,7 @@ describe("ClaudeAdapterV2 background wake turns", () => {
yield* changes.until(
() => events.filter((event) => event.type === "turn.terminal").length === 2,
);
assert.isFalse(yield* hasPendingBackgroundWork);
assert.isTrue(yield* hasPendingBackgroundWork);
assert.lengthOf(continuationRequests, 0);

yield* Queue.offer(secondProcess, wakeResult);
Expand Down
12 changes: 11 additions & 1 deletion apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13749,14 +13749,24 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* (
return true;
}
}
// An empty roster can precede task_notification; the wake it owes is still pending.
for (const taskIds of (yield* Ref.get(
wakeEligibleBackgroundTasksByNativeThread,
)).values()) {
if (taskIds.size > 0) {
return true;
}
}
const buffers = yield* Ref.get(wakeBuffers);
for (const entry of buffers.values()) {
if (
entry.messages.some(
(message) =>
message.type === "user" ||
message.type === "assistant" ||
message.type === "result",
message.type === "result" ||
// The roster empties on the notification, before the wake turn's frames arrive.
(message.type === "system" && message.subtype === "task_notification"),
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
)
) {
return true;
Expand Down
Loading
Loading