Skip to content

Commit 4e6ee27

Browse files
authored
🤖 fix: don't persist compaction model as workspace AI settings (#1259)
## Problem When compaction is triggered with a specific model (e.g., `/compact -m openai:gpt-4.1-mini`), the backend was incorrectly persisting that model to the workspace's AI settings, overwriting the user's preferred model. After compaction completed, the workspace would be set to use the compaction model instead of the model the user had selected. ## Root Cause Regression introduced in 1ae0377 (#1203) which added a "safety net" to persist AI settings from `sendMessage`/`resumeStream` for cross-device consistency. This safety net didn't account for compaction requests which intentionally use a different (often cheaper/faster) model. cc @tomaskoubek (regressor) ## Fix Skip persisting AI settings when: - `sendMessage` is called with `muxMetadata.type === "compaction-request"` - `resumeStream` is called with `options.mode === "compact"` ## Testing - Added IPC test verifying compaction requests don't override workspace AI settings - Existing `updateAISettings` test still passes --- _Generated with `mux` • Model: `anthropic:claude-opus-4-5` • Thinking: `high`_
1 parent c836a85 commit 4e6ee27

File tree

2 files changed

+81
-34
lines changed

2 files changed

+81
-34
lines changed

src/node/services/workspaceService.ts

Lines changed: 29 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -959,6 +959,33 @@ export class WorkspaceService extends EventEmitter {
959959
return { model, thinkingLevel };
960960
}
961961

962+
/**
963+
* Best-effort persist AI settings from send/resume options.
964+
* Skips compaction requests which use a different model intentionally.
965+
*/
966+
private async maybePersistAISettingsFromOptions(
967+
workspaceId: string,
968+
options: SendMessageOptions | undefined,
969+
context: "send" | "resume"
970+
): Promise<void> {
971+
// Skip for compaction - it may use a different model and shouldn't override user preference
972+
const isCompaction = options?.mode === "compact";
973+
if (isCompaction) return;
974+
975+
const extractedSettings = this.extractWorkspaceAISettingsFromSendOptions(options);
976+
if (!extractedSettings) return;
977+
978+
const persistResult = await this.persistWorkspaceAISettings(workspaceId, extractedSettings, {
979+
emitMetadata: false,
980+
});
981+
if (!persistResult.success) {
982+
log.debug(`Failed to persist workspace AI settings from ${context} options`, {
983+
workspaceId,
984+
error: persistResult.error,
985+
});
986+
}
987+
}
988+
962989
private async persistWorkspaceAISettings(
963990
workspaceId: string,
964991
aiSettings: WorkspaceAISettings,
@@ -1275,23 +1302,7 @@ export class WorkspaceService extends EventEmitter {
12751302
};
12761303

12771304
// Persist last-used model + thinking level for cross-device consistency.
1278-
// Best-effort: failures should not block sending.
1279-
const extractedSettings = this.extractWorkspaceAISettingsFromSendOptions(resolvedOptions);
1280-
if (extractedSettings) {
1281-
const persistResult = await this.persistWorkspaceAISettings(
1282-
workspaceId,
1283-
extractedSettings,
1284-
{
1285-
emitMetadata: false,
1286-
}
1287-
);
1288-
if (!persistResult.success) {
1289-
log.debug("Failed to persist workspace AI settings from send options", {
1290-
workspaceId,
1291-
error: persistResult.error,
1292-
});
1293-
}
1294-
}
1305+
await this.maybePersistAISettingsFromOptions(workspaceId, resolvedOptions, "send");
12951306

12961307
if (this.aiService.isStreaming(workspaceId) && !resolvedOptions?.editMessageId) {
12971308
const pendingAskUserQuestion = askUserQuestionManager.getLatestPending(workspaceId);
@@ -1405,23 +1416,7 @@ export class WorkspaceService extends EventEmitter {
14051416
const session = this.getOrCreateSession(workspaceId);
14061417

14071418
// Persist last-used model + thinking level for cross-device consistency.
1408-
// Best-effort: failures should not block resuming.
1409-
const extractedSettings = this.extractWorkspaceAISettingsFromSendOptions(options);
1410-
if (extractedSettings) {
1411-
const persistResult = await this.persistWorkspaceAISettings(
1412-
workspaceId,
1413-
extractedSettings,
1414-
{
1415-
emitMetadata: false,
1416-
}
1417-
);
1418-
if (!persistResult.success) {
1419-
log.debug("Failed to persist workspace AI settings from resume options", {
1420-
workspaceId,
1421-
error: persistResult.error,
1422-
});
1423-
}
1424-
}
1419+
await this.maybePersistAISettingsFromOptions(workspaceId, options, "resume");
14251420

14261421
const result = await session.resumeStream(options);
14271422
if (!result.success) {

tests/ipc/workspaceAISettings.test.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,56 @@ describe("workspace.updateAISettings", () => {
4848
await cleanupTempGitRepo(tempGitRepo);
4949
}
5050
}, 60000);
51+
52+
test("compaction requests do not override workspace aiSettings", async () => {
53+
const env: TestEnvironment = await createTestEnvironment();
54+
const tempGitRepo = await createTempGitRepo();
55+
56+
try {
57+
const branchName = generateBranchName("ai-settings-compact");
58+
const createResult = await createWorkspace(env, tempGitRepo, branchName);
59+
if (!createResult.success) {
60+
throw new Error(`Workspace creation failed: ${createResult.error}`);
61+
}
62+
63+
const workspaceId = createResult.metadata.id;
64+
expect(workspaceId).toBeTruthy();
65+
66+
const client = resolveOrpcClient(env);
67+
68+
// Set initial workspace AI settings
69+
const updateResult = await client.workspace.updateAISettings({
70+
workspaceId: workspaceId!,
71+
aiSettings: { model: "anthropic:claude-sonnet-4-20250514", thinkingLevel: "medium" },
72+
});
73+
expect(updateResult.success).toBe(true);
74+
75+
// Send a compaction request with a different model
76+
// The muxMetadata type: "compaction-request" should prevent AI settings from being persisted
77+
await client.workspace.sendMessage({
78+
workspaceId: workspaceId!,
79+
message: "Summarize the conversation",
80+
options: {
81+
model: "openai:gpt-4.1-mini", // Different model for compaction
82+
thinkingLevel: "off",
83+
mode: "compact",
84+
muxMetadata: {
85+
type: "compaction-request",
86+
rawCommand: "/compact",
87+
parsed: {},
88+
},
89+
},
90+
});
91+
92+
// Verify the original workspace AI settings were NOT overwritten
93+
const info = await client.workspace.getInfo({ workspaceId: workspaceId! });
94+
expect(info?.aiSettings).toEqual({
95+
model: "anthropic:claude-sonnet-4-20250514",
96+
thinkingLevel: "medium",
97+
});
98+
} finally {
99+
await cleanupTestEnvironment(env);
100+
await cleanupTempGitRepo(tempGitRepo);
101+
}
102+
}, 60000);
51103
});

0 commit comments

Comments
 (0)