fix(ask): avoid numeric enum in round-zero schema for Cloud Code Assist - #4606
Conversation
z.literal(0) serializes to {"type":"number","enum":[0]}, which Cloud
Code Assist API rejects with HTTP 400 (expects TYPE_STRING for enum
values). Replace with an int range [0,0] that keeps round pinned to zero
without emitting a numeric enum.
Yeachan-Heo
left a comment
There was a problem hiding this comment.
MERGE_READY
Independent adversarial review of head db09c22 against origin/dev 7ca1d66a (lane branch review/pr-4606-current-head-1786958478). The defect, mechanism, and fix were all independently reproduced before this verdict.
Defect — independently confirmed
z.literal(0) in DeepInterviewTopologyMeta (packages/coding-agent/src/tools/ask-contract.ts:102) serializes via z.toJSONSchema to {"type":"number","const":0}. The Cloud Code Assist path (normalizeAntigravityTools → normalizeSchemaForCCA) forbids const (CCA_FORBIDDEN_KEYS) and rewrites it to enum: [0] (normalizeSchemaForCCA const→enum conversion). The serialized antigravity tool declaration therefore carries a numeric enum, which the CCA API rejects with the exact 400 Invalid value at ... enum[0]' (TYPE_STRING), 0 from the PR description. Any google-antigravity session whose active deep-interview stage is topology fails before the first assistant turn.
Scope check on the old form across every ask surface: only selectAskParameters("topology") emits the numeric enum; the deferred askSchema union, post-topology, and ordinary forms do not (their round constraints are positive ranges, and the union collapses to a single variant). This matches the reported blast radius exactly.
Fix — verified
z.number().int().min(0).max(0) emits {"type":"integer","minimum":0,"maximum":0}. On the CCA wire this becomes {"type":"integer","description":"Round 0 topology confirmation\n\n{minimum: 0, maximum: 0}"} — minimum/maximum are stripped into the liftable-to-description spill, the identical treatment the adjacent ambiguity field already receives. No numeric enum remains anywhere in the serialized schema (verified by regex over the full serialized declaration).
Runtime contract is unchanged: round: 0 accepted; 1, -1, 0.5 rejected (full valid Round-0 payload with intent_contract). The TS-level widening of the inferred type from 0 to number is safe — every consumer of this field does runtime value checks (=== 0, <= 0), and check:ts passes.
Red-team checklist
- Regression: none found. This is the same constraint-relaxation pattern the file already uses for
ambiguity(min(0).max(1)) and the siblingroundfields (positive()). - Lifecycle/authority/privacy/security: no behavioral, permission, or data-surface change; schema-shape only, tool-input validation preserved exactly.
- Generated/aggregate fixture closure:
tool-catalog.generated.ts(38 entries),schemas/*.schema.json, plugins, and docs-index all regenerate byte-identical — the catalog snapshot uses the ordinary ask form, which does not contain this field. - Stale base: fork point
c83ffe3dvs dev tip7ca1d66a; GitHub three-dot diff is exactly this one file; dev drift (0.14.0 version bumps, README/MAINTAINERS/terminal-app-integrations docs) has zero interaction. Merges clean. - Targeted tests: deep-interview suites (skill-contract, workflow-gates, gate-redteam, hangul-normalization, plaintext-gate-guard, acp-wire, session-continuation) + sdk-ask suites + tools/descriptors + tools-registry + default-gjc-definitions: 162 pass / 0 fail.
- Full gates:
bun run check:tsfull closure (all workspaces, schemas, plugins, sdk-closure manifests) exit 0. - Build + smoke:
bun run buildexit 0; compiled binary--version→gjc/0.14.0,--smoke-test→ ok. - Local-only noise: a handful of
packages/aitests (baseurl-trust, register-builtins watchdogs, auth-broker-wire, auth-storage-mcp-origin, auth-gateway-*, openai-responses-cache-affinity) fail in this sandbox identically on pristineorigin/dev— machine config (ANTHROPIC_BASE_URL=api.layofflabs.comin the ambient env, fetch-spy/global-fetch interception), not this PR.
Notes (non-blocking)
- No changelog entry under
packages/coding-agent/CHANGELOG.md [Unreleased]; dev history shows fix entries are conventionally recorded. Merge will add one so the release notes carry the CCA 400 fix. - The PR description's claim that zod emits
enum:[0]is imprecise (zod 4.4.3 emitsconst:0; the CCA normalizer converts it toenum), but the end-to-end diagnosis and fix are correct.
Proceeding to merge into dev under delegated authority, followed by canonical dev fast-forward, bun run build, and dogfood on the merged tree.
—
[repo owner's gaebal-gajae (clawdbot) 🦞]
The merged fix removes the numeric enum from the deep-interview Round-0 topology ask schema on the Cloud Code Assist wire, unblocking google-antigravity deep-interview sessions that previously died with HTTP 400 before the first assistant turn. Records it under [Unreleased] per repo convention. Lore-id: 8f2c1e94 Constraint: changelog entries go under [Unreleased]; released sections are never edited Confidence: high Scope-risk: narrow Reversibility: trivial Tested: merged dev targeted suites in the review lane (162 pass) Supersedes: eee2a91 c243017c0e
…#4606 The merged fix removes the numeric enum from the deep-interview Round-0 topology ask schema on the Cloud Code Assist wire, unblocking google-antigravity deep-interview sessions that previously died with HTTP 400 before the first assistant turn. Records it under [Unreleased] per repo convention. Lore-id: 8f2c1e94 Constraint: changelog entries go under [Unreleased]; released sections are never edited Confidence: high Scope-risk: narrow Reversibility: trivial Tested: merged dev targeted suites in the review lane (162 pass) Supersedes: eee2a91 c243017c0e (cherry picked from commit b91aece)
Problem
The
asktool's deep-interview round-zero schema usesz.literal(0):When serialized to JSON Schema this emits
{"type": "number", "enum": [0]}. The Cloud Code Assist API (google-antigravity provider) rejects numeric enum values with:Any session on the
google-antigravityprovider that activates the deep-interview skill prompt fails with HTTP 400 before the first assistant turn.Fix
Replace
z.literal(0)with an integer range pinned to zero:This keeps the runtime contract identical (only
0is accepted — verified by schema tests) while avoiding the numericenumthat Cloud Code Assist rejects. String literals elsewhere (component,dimension) are unaffected because their enums are already strings.Verification
bun test packages/coding-agent/test/deep-interview-*andsdk-ask-*: 76 pass / 0 fail0, rejects1and-1(same contract asz.literal(0))google-antigravitypreviously 400'd; with this change the schema no longer contains a numeric enum