Skip to content

fix(tools): repair integral floats in native u64 tool fields - #2371

Merged
lidge-jun merged 1 commit into
devfrom
codex/wp3-u64-timeout
Aug 22, 2026
Merged

fix(tools): repair integral floats in native u64 tool fields#2371
lidge-jun merged 1 commit into
devfrom
codex/wp3-u64-timeout

Conversation

@lidge-jun

@lidge-jun lidge-jun commented Aug 22, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes #2316. Grok-routed Codex rejects multi_agent_v1__wait_agent before the tool runs:

failed to parse function arguments: invalid type: floating point `120000.0`, expected u64

Codex advertises timeout_ms as a JSON Schema number, but its Rust runtime deserializes the field as u64. Grok serializes the integer through a float, so a wait of 120000 arrives as 120000.0.

The #1611 integral-float repair already existed and was correctly wired — it just declined here, because declaresInteger only fires on a declared integer (tool-argument-integers.ts:36-41).

The schema lookup was never the problem. The error text comes from Codex's own deserializer, which only sees the call after the bridge emitted it — and the reported call is the namespaced name, so it resolved fine. That rules out the bare-name theory: src/bridge.ts:1041 and :1788 reject undeclared names with a 502 before argument repair runs, so a schema registered under a bare key alone could never be consulted.

So this is a one-file change: treat a known native u64 field as integer-declared when the schema declares a numeric type, and let the existing re-stringify path emit 120000.

Why the allowlist is one field wide

U64_NUMBER_FIELDS = {"timeout_ms"} names only what has a captured u64 rejection. The repair is unambiguous only for a field that cannot legitimately hold a fraction — a generic name like start, end, priority, or port would silently rewrite a third-party tool's valid fractional value.

Cursor's sibling yield_time_ms (tool-definitions.ts:49) is also declared number and is deliberately excluded: no rejection has been captured against it. It gets its own change when it gets its own reproduction.

This is the main difference from #2360, which fixes the same issue with a 10-name allowlist, repairs even when no schema is present, and re-adds a bare-name alias without the uniqueness guard at collaboration.ts:154.

Verification

bun test tests/tool-argument-integers.test.ts    33 pass / 0 fail / 43 expect()
bun x tsc --noEmit                               exit 0

Red-green proof — reverting only the source change fails exactly the three tests that pin the fix, and nothing else:

(fail) repairs the exact wait_agent call from the report
(fail) the allowlist reaches a nested object, not just the top level
(fail) the namespaced wait_agent call is repaired through the real bridge
 30 pass, 3 fail

Nine tests cover: the reported call, a fractional value still failing honestly, temperature: 1.0 staying byte-identical, an already-clean payload preserving its bytes, nested objects, an array named like the allowlist not inheriting it, an allowlisted name over a string field, non-allowlisted fields keeping their floats, and both bridge paths end to end.

Checklist

  • Focused regression test added, proven to fail without the fix
  • bun x tsc --noEmit clean
  • No behavior change for any field outside the allowlist
  • Full suite and exact-head CI run against the merged head

Closes #2316

Summary by CodeRabbit

  • Bug Fixes

    • Fixed handling of integral timeout values for multi-agent wait operations, preventing valid requests from being rejected.
    • Preserved fractional values and unrelated numeric fields without unintended conversion.
    • Applied the correction consistently across streaming and non-streaming responses.
  • Tests

    • Added coverage for nested fields, arrays, string values, unchanged payloads, and namespaced wait operations.
  • Documentation

    • Added an execution record covering merged and held work, review evidence, verification results, and recovery details.

Codex advertises multi_agent_v1__wait_agent's timeout_ms as a JSON Schema
number, but its Rust runtime deserializes the field as u64. Grok serializes
the integer through a float, so a wait of 120000 arrives as 120000.0 and
Codex rejects the call before the tool runs, with an invalid-type error
naming a floating point value where u64 was expected.

The #1611 repair already existed but declined here, because it only fires on
a declared integer. The schema lookup was never the problem: the error text
comes from Codex's own deserializer, which only sees the call after the
bridge emitted it.

Treat a known native u64 field as integer-declared when the schema declares a
numeric type, so the existing re-stringify path emits 120000. A fractional
value is a real disagreement and still fails upstream.

The allowlist is one field wide on purpose. It names only what has a captured
u64 rejection, because the repair is unambiguous only for a field that cannot
hold a fraction; a generic name like start, priority, or port would silently
rewrite a third-party tool's legitimate fractional value. Cursor's sibling
yield_time_ms is also declared number and is deliberately not included: it
gets its own change when it gets its own reproduction.

Array items are judged by their own schema rather than inheriting the key, so
an array named like the allowlist is not rewritten.

Closes #2316
@lidge-jun
lidge-jun requested a review from Ingwannu as a code owner August 22, 2026 09:46
@github-actions

Copy link
Copy Markdown
Contributor

Deterministic PR hygiene checks passed.

@github-actions github-actions Bot added the bug Something isn't working label Aug 22, 2026
@coderabbitai

coderabbitai Bot commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 974d3c28-bd03-43a4-9083-ddeaa69be73c

📥 Commits

Reviewing files that changed from the base of the PR and between 5921c20 and a9cb766.

📒 Files selected for processing (3)
  • devlog/_plan/260822_backlog_disposition_program/011_wp1_execution_record.md
  • src/lib/tool-argument-integers.ts
  • tests/tool-argument-integers.test.ts

📝 Walkthrough

Walkthrough

The change adds scoped repair for integral timeout_ms values advertised as JSON Schema number but consumed as native Rust u64. It adds direct and bridge-path tests, and records WP1 dispositions, repository findings, commit recovery, and verification results.

Changes

Native u64 timeout repair

Layer / File(s) Summary
Allowlisted timeout coercion
src/lib/tool-argument-integers.ts:62-79, src/lib/tool-argument-integers.ts:139-161, src/lib/tool-argument-integers.ts:183-184, src/lib/tool-argument-integers.ts:201
Adds the timeout_ms allowlist. Recursive coercion receives object property names, applies integer repair to compatible numeric schemas, and does not pass names from arrays to their items.
Direct and bridge-path verification
tests/tool-argument-integers.test.ts:262-363
Tests integral and fractional timeouts, nested objects, array scope, string fallback, allowlist scope, byte preservation, and namespaced streaming and non-streaming bridge paths.

WP1 execution record

Layer / File(s) Summary
Disposition and verification record
devlog/_plan/260822_backlog_disposition_program/011_wp1_execution_record.md:1-53, devlog/_plan/260822_backlog_disposition_program/011_wp1_execution_record.md:85-103
Records merged and held PRs, review evidence, the PR #2359 live-model finding, and focused verification results.
Repository and commit handling corrections
devlog/_plan/260822_backlog_disposition_program/011_wp1_execution_record.md:55-83
Corrects branch-protection details and documents recovery of an unpushed commit after a shared-checkout hard reset.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CodexApp
  participant bridgeToResponsesSSE
  participant buildResponseJSON
  participant coerceValue
  participant wait_agent
  CodexApp->>bridgeToResponsesSSE: send multi_agent_v1__wait_agent arguments
  bridgeToResponsesSSE->>coerceValue: coerce timeout_ms
  coerceValue-->>bridgeToResponsesSSE: return integer timeout_ms
  bridgeToResponsesSSE->>wait_agent: execute repaired arguments
  CodexApp->>buildResponseJSON: send multi_agent_v1__wait_agent arguments
  buildResponseJSON->>coerceValue: coerce timeout_ms
  coerceValue-->>buildResponseJSON: return integer timeout_ms
  buildResponseJSON->>wait_agent: execute repaired arguments
Loading

Suggested reviewers: ingwannu

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/wp3-u64-timeout

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@lidge-jun
lidge-jun merged commit ae05672 into dev Aug 22, 2026
22 of 23 checks passed
@lidge-jun
lidge-jun deleted the codex/wp3-u64-timeout branch August 22, 2026 09:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant