With CLAUDE_PLAN_OPUS=1, a main-loop request that is exactly sonnet at medium effort in plan mode is sent as opus at high effort. Any other model, effort, mode…

My personal Claude Code plugins repository. Contains multiple plugins for different workflows.
| Plugin | Description |
|---|---|
dotclaudefiles | Commands, agents, skills, and configs I use daily |
# Add marketplace (once)
/plugin marketplace add DieGopherLT/dotclaudefiles
# Install plugin
/plugin install dotclaudefiles@diegopher
⚠️ Warning: This will replace your local files with the repo version (backups are created automatically).
# Bash
./scripts/stow-claude.sh
# Fish
./scripts/stow-claude.fish
Want a status line? Use dotfiles/claude/.config/ccstatusline/settings.json with:
ccstatusline (Node.js) sirmalloc/ccstatusline - Feature-rich status line with extensive customization.
or
cc-status-line (Go) Install DieGopherLT/cc-status-line - Lightweight Go implementation with better performance.
They both look almost identical, Go version made by me includes a visual context indicator.
/plugin marketplace update diegopher
/plugin install dotclaudefiles@diegopher
/plugin uninstall dotclaudefiles@diegopher
Diego (@DieGopherLT)
hooks/register.ts 44 lines1import type { Register } from 'claude-code'
2
3// turn.step takes the resolved id, as e.model carries it: the 'opus' alias is
4// refused by the API. shortcut: pinned id, bump it when a newer Opus ships.
5const PLAN_MODEL = 'claude-opus-5-5'
6
7export const register: Register = on => {
8 // shortcut: one transcript line per decision change to verify the mode and
9 // model strings live, drop once confirmed.
10 let lastDecision = ''
11 let permissionMode: string | undefined
12 let isEnabled: Promise<boolean> | undefined
13
14 // Every classic event carries the live permission mode; Shift+Tab and
15 // Enter/ExitPlanMode show up at the next one, before the next model request.
16 on('classic.*', async ($, e, next) => {
17 permissionMode = e.permission_mode
18 return next(e)
19 })
20
21 on('turn.step', async function* ($, e, next) {
22 isEnabled ??= $.env.get('CLAUDE_PLAN_OPUS').then(value => value === '1')
23
24 // Only the exact session default (sonnet at medium) is upgraded, so an
25 // explicit /model or /effort choice is never overridden.
26 const isUpgrade =
27 (await isEnabled) &&
28 e.agentId === undefined &&
29 permissionMode === 'plan' &&
30 /sonnet/i.test(e.model) &&
31 e.effort === 'medium'
32
33 if (e.agentId === undefined) {
34 const decision = `mode=${permissionMode} model=${e.model} effort=${e.effort} upgraded=${isUpgrade}`
35 if (decision !== lastDecision) {
36 lastDecision = decision
37 $.ui.log(`plan-opus-upgrade: ${decision}`)
38 }
39 }
40
41 return yield* next(isUpgrade ? { ...e, model: PLAN_MODEL, effort: 'high' } : e)
42 })
43}
44