Repository navigation
Description
Summary
When an orchestrator task processes a user message that contains a slash command with a mode: frontmatter field (e.g. /commit, /release, /cli-release), handleModeSwitch is called on the orchestrator task itself rather than being scoped to a pending child. This permanently overwrites the orchestrator's stored mode and loads the API profile saved for the target mode, causing the task to appear in the wrong mode for the rest of its life.
Steps to Reproduce
- Open Zoo Code in VS Code.
- Set the active mode to Orchestrator and the active API profile to chatgpt+ (or any profile that is NOT the saved profile for
codemode). - Start a new task.
- Send a first message that contains or triggers a slash command whose frontmatter declares
mode: code— for example/commit,/release, or/cli-release(all three ship withmode: codein.roo/commands/). - Observe the mode indicator and API profile in the UI immediately after the message is processed.
Expected: The orchestrator task remains in Orchestrator mode with chatgpt+ for its own turns. Any child task spawned via new_task uses code mode as intended.
Actual: The orchestrator task switches to code mode and its sticky API profile is overwritten with the profile saved for code mode (e.g. glm). Both fields are persisted to history_item.json and survive task resumption.
Root Cause
In src/core/task/Task.ts around line 3094, after processUserContentMentions returns a slashCommandMode from the command's frontmatter:
// Switch mode if specified in a slash command's frontmatter
if (slashCommandMode) {
const provider = this.providerRef.deref()
if (provider) {
const state = await provider.getState()
const targetMode = getModeBySlug(slashCommandMode, state?.customModes)
if (targetMode) {
await provider.handleModeSwitch(slashCommandMode) // ← bug
}
}
}handleModeSwitch is called with no targetTask, so it defaults to this.getCurrentTask() — the orchestrator itself. This triggers:
updateTaskHistory({ ...taskHistoryItem, mode: "code" })— writesmode: "code"tohistory_item.jsonfor the orchestrator.(task as any)._taskMode = "code"— updates the in-memory mode.activateProviderProfileUnlocked(...)— loads the saved API profile forcodemode (e.g.glm) and persists it asapiConfigNameon the orchestrator's history item viapersistStickyProviderProfileToCurrentTask.
The handleModeSwitch signature already accepts null as targetTask with a documented meaning of "apply only global mode/profile effects for a pending child" (JSDoc at the method definition). The fix is to pass null so the orchestrator's own history item is not modified.
Proposed Fix
// Pass null so only the global UI state is updated;
// the orchestrator's own mode and API profile are not overwritten.
await provider.handleModeSwitch(slashCommandMode, null)Verification
The bug can be confirmed by inspecting history_item.json for the orchestrator task under globalStorage/zoocodeorganization.zoo-code/tasks/<taskId>/. After the first message, the file shows "mode":"code" and "apiConfigName":"glm" even though the task was started in Orchestrator mode with a different profile. The first API call's environment_details block (in api_conversation_history.json) correctly shows <slug>orchestrator</slug>, confirming the task was started in the right mode.
Relation to Prior Work
PR #1637 fixed task-local mode isolation for environment details and tool validation (presentAssistantMessage.ts, getEnvironmentDetails.ts) but did not touch Task.ts:3094, so this path remains unfixed.
Affected Commands
Any slash command in .roo/commands/ with a mode: frontmatter field, including the built-in /commit, /release, and /cli-release (all declare mode: code).
Activity