Summary
Reasoning-effort selection from the app never reaches Claude. The CLI always runs at the default (medium) regardless of what the user picks, because the effort field is stripped from the incoming message metadata by the Zod schema before runClaude.ts reads it.
This affects all effort levels (low/medium/high/xhigh/max), not just xhigh.
Root cause
MessageMetaSchema in packages/happy-cli/src/api/types.ts does not include an effort field:
export const MessageMetaSchema = z.object({
sentFrom: z.string().optional(),
permissionMode: z.enum([...]).optional(),
model: z.string().nullable().optional(),
fallbackModel: z.string().nullable().optional(),
customSystemPrompt: z.string().nullable().optional(),
appendSystemPrompt: z.string().nullable().optional(),
allowedTools: z.array(z.string()).nullable().optional(),
disallowedTools: z.array(z.string()).nullable().optional()
// <-- no `effort`
})
z.object() strips unknown keys by default, so message.meta.effort is removed during parsing. runClaude.ts then never sees it:
if (message.meta?.hasOwnProperty('effort')) { ... } // always false
else { logger.debug(`[loop] User message received with no effort override, using current: ${currentEffort ?? 'default'}`) }
The feat: pass through xhigh effort for Opus 4.8 change (commit 004338ca) widened VALID_EFFORTS, QueryOptions.effort, and the SDK types — but did not add effort to this wire schema, so the value is dropped one layer earlier.
Proof (from a live session log)
The app does send effort correctly. A single incoming user message contained:
{
"role": "user",
"content": { "type": "text", "text": "test" },
"meta": { "sentFrom": "web", "appendSystemPrompt": "...", "effort": "max" }
}
For that exact message, the loop logged:
[loop] Append system prompt updated from user message: set <-- appendSystemPrompt (in schema) honored
[loop] User message received with no effort override, using current: medium <-- effort (not in schema) dropped
Same meta object, same message.meta?.hasOwnProperty(...) access pattern — the only difference is that appendSystemPrompt is in MessageMetaSchema and effort is not. This pinpoints the schema as the strip point.
Proposed fix
Add effort to MessageMetaSchema:
disallowedTools: z.array(z.string()).nullable().optional(),
effort: z.string().nullable().optional() // Reasoning effort for this message (null = reset); validated against VALID_EFFORTS in runClaude
Verified locally: after adding this one field and rebuilding happy-cli, messages now log Effort updated from user message: xhigh and the level reaches the SDK.
Version
- happy-cli
1.1.10-beta.7
- Still present on current
main (6db7f2c6) — MessageMetaSchema has no effort field there either.
Summary
Reasoning-effort selection from the app never reaches Claude. The CLI always runs at the default (
medium) regardless of what the user picks, because theeffortfield is stripped from the incoming message metadata by the Zod schema beforerunClaude.tsreads it.This affects all effort levels (
low/medium/high/xhigh/max), not justxhigh.Root cause
MessageMetaSchemainpackages/happy-cli/src/api/types.tsdoes not include aneffortfield:z.object()strips unknown keys by default, somessage.meta.effortis removed during parsing.runClaude.tsthen never sees it:The
feat: pass through xhigh effort for Opus 4.8change (commit004338ca) widenedVALID_EFFORTS,QueryOptions.effort, and the SDK types — but did not addeffortto this wire schema, so the value is dropped one layer earlier.Proof (from a live session log)
The app does send
effortcorrectly. A single incoming user message contained:{ "role": "user", "content": { "type": "text", "text": "test" }, "meta": { "sentFrom": "web", "appendSystemPrompt": "...", "effort": "max" } }For that exact message, the loop logged:
Same
metaobject, samemessage.meta?.hasOwnProperty(...)access pattern — the only difference is thatappendSystemPromptis inMessageMetaSchemaandeffortis not. This pinpoints the schema as the strip point.Proposed fix
Add
efforttoMessageMetaSchema:Verified locally: after adding this one field and rebuilding happy-cli, messages now log
Effort updated from user message: xhighand the level reaches the SDK.Version
1.1.10-beta.7main(6db7f2c6) —MessageMetaSchemahas noeffortfield there either.