Skip to content

effort selection never reaches Claude — effort missing from MessageMetaSchema (stripped by Zod) #1375

Description

@a-preskenis

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    Status
    Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions