fix(cli): accept the app's effort key in MessageMetaSchema - #1674
Open
chphch wants to merge 1 commit into
Open
Conversation
The per-message effort picker never reaches the SDK. happy-app sends the
value under `meta.effort` (sync.ts:728), the CLI parses the message with
`UserMessageSchema.safeParse` (apiSession.ts:552), and `MessageMetaSchema`
does not declare `effort` — so Zod, which strips unknown keys by default,
deletes it before anything downstream can see it.
runClaude then gates on the key that is already gone:
if (message.meta?.hasOwnProperty('effort')) { // always false
const incoming = (message.meta as Record<string, unknown>).effort;
That cast is the tell: the key is read through `Record<string, unknown>`
precisely because it is absent from the type, so typecheck passes while the
branch that applies the picked effort is unreachable. Every user message
falls through to the `else` and keeps the previous effort.
Reproduced against this file, before and after:
app sends {"sentFrom":"app","effort":"high"}
parsed {"sentFrom":"app"} <- effort dropped
guard meta?.hasOwnProperty('effort') = false
with this change:
parsed {"sentFrom":"app","effort":"high"}
guard meta?.hasOwnProperty('effort') = true
The existing coverage cannot catch it: runClaude.test.ts builds `meta` as a
TypeScript object literal and never goes through the schema, so the wire path
is the only place the mismatch exists.
Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The effort picker never reaches the SDK
happy-app sends the per-message effort under
meta.effort(sync.ts:728). The CLI parses incoming messages withUserMessageSchema.safeParse(apiSession.ts:552), andMessageMetaSchemadoes not declareeffort— so Zod, which strips unknown keys by default, removes it before anything downstream sees it.runClaudethen gates on the key that is already gone:The cast is the tell.
effortis read throughRecord<string, unknown>precisely because it is absent fromMessageMeta, so typecheck passes while the branch that applies the picked effort is unreachable. Every user message falls through to theelseand keeps whatever effort the session was spawned with.Reproduction
Parsing a message shaped exactly like the one the app sends, against this file before and after the change:
metaafter parsemeta?.hasOwnProperty('effort')main{"sentFrom":"app"}false{"sentFrom":"app","effort":"high"}trueInput in both cases:
{"sentFrom":"app","effort":"high"}.Why the tests don't catch it
runClaude.test.tsbuildsmetaas a TypeScript object literal and hands it straight to the loop, so it never crosses the schema. The mismatch exists only on the wire path, which no test exercises.The change
One field on
MessageMetaSchema. Nullable and optional to match the reset semantics the surrounding fields use, and the samenull = resetbehaviourrunClaudealready implements.CI on this PR fails at the install step for the reason in #1663 (
pnpm-lock.yamlout of sync withhappy-cli/package.jsonsince e7e0ff6), not because of this change.