fix: treat empty-string optional integer tool params as unset#100273
fix: treat empty-string optional integer tool params as unset#100273snotty wants to merge 1 commit into
Conversation
Optional positive-integer tool params (e.g. Telegram replyTo/threadId) threw ToolInputError when a tool-calling model populated them with an empty-string or whitespace-only default. Those defaults carry no value, so readPositiveIntegerParam/readNonNegativeIntegerParam now treat a blank string as unset (undefined) instead of throwing, while still rejecting genuinely invalid present values (0, "42.5", "-3"). This prevents silent message-delivery failures when models emit empty routing-param defaults. Adds unit tests covering blank vs invalid.
|
Codex review: needs maintainer review before merge. Reviewed July 5, 2026, 8:41 AM ET / 12:41 UTC. Summary PR surface: Source +19, Tests +31. Total +50 across 2 files. Reproducibility: yes. Current main ignores blank strings in readNumberParam but the integer wrappers still throw because the raw value is non-null, and Telegram routes reply/thread params through those wrappers; Mantis also captured the before/after Telegram path. Review metrics: 1 noteworthy metric.
Root-cause cluster Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Risk before merge
Maintainer options:
Next step before merge
Maintainer decision needed
Security Review detailsBest possible solution: Land the shared-reader fix after maintainers accept blank strings as unset for optional integer helpers and exact-head CI is green. Do we have a high-confidence way to reproduce the issue? Yes. Current main ignores blank strings in readNumberParam but the integer wrappers still throw because the raw value is non-null, and Telegram routes reply/thread params through those wrappers; Mantis also captured the before/after Telegram path. Is this the best way to solve the issue? Yes, if maintainers accept the SDK behavior change. The shared helper is the narrowest owner-boundary fix; channel-local sanitizers are the safer compatibility alternative but would duplicate the invariant. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against deac98eb7204. Label changesLabel changes:
Label justifications:
Evidence reviewedPR surface: Source +19, Tests +31. Total +50 across 2 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
Review history (1 earlier review cycle)
|
Mantis Telegram Desktop ProofSummary: Mantis captured native Telegram Desktop before/after GIFs showing the blank routing-field send missing on Main and delivered in this PR.
Motion-trimmed clips: |




Closes #100269
What Problem This Solves
Fixes an issue where agent-initiated and scheduled (cron) Telegram messages would silently fail to deliver when the driving model filled the optional
replyTo/threadId(orreplyToMessageId/messageThreadId) fields with an empty-string default. The user never receives the message — most painfully, time-sensitive cron reminders that run, draft correctly, then fail only at the delivery step.Modern tool-calling models routinely emit every optional schema field with an empty (
"") or zero ("0") default even when the caller isn't setting it. For those Telegram routing hints the empty default causedToolInputError: replyTo must be a positive integer.(and thethreadIdvariant); the agent then retried with more invalid defaults and gave up. This reproduced identically across two independent providers (an MAI Flash-family model and GitHub Copilotgpt-5-mini), confirming it is model-agnostic and rooted in the shared parameter reader rather than any one model.Why This Change Was Made
readPositiveIntegerParam/readNonNegativeIntegerParaminsrc/agents/tools/common.tsthrew whenever the parsed value wasundefinedbut the raw param was notnull. An empty/whitespace-only string parses toundefinedyet is!= null, so it threw — even though an empty string is semantically "not provided". These readers now treat a blank string as unset (returnundefined) while still rejecting genuinely invalid present values (numeric0,"42.5","-3", over-max). This mirrors howreadStringParam/readNumberParamalready ignore empty/whitespace strings, and fixes every channel that reads routing params through these helpers (Telegram, plus the same pattern in Discord/Slack/Matrix and the core message-action-runner) in a single place. The existing "timeoutMs: 0throws" contract is preserved.User Impact
Agent- and cron-initiated messages (Telegram especially) now deliver reliably regardless of which model drives the
messagetool. Empty optional routing-param defaults are ignored instead of aborting the send, so scheduled reminders and notifications stop silently disappearing. No API or behavior change for callers that pass real values; invalid present values are still rejected exactly as before.Evidence
Targeted unit run of the touched test file (19 pre-existing + 2 new cases), all green:
New tests assert both the fix and the preserved strictness:
Real-world before/after (Telegram cron send via the
messagetool, model emittingreplyTo:""/threadId:""):ToolInputError: replyTo must be a positive integer.×N, delivery fails, no message received.{ "ok": true, "messageId": "…" }, message delivered, zeroToolInputError.Full
pnpm build && pnpm check && pnpm testwas not run locally (the monorepo-wide typecheck exhausted memory on my constrained ARM/WSL box); I relied on the scoped vitest lane above and am leaving CI to validate the wider suite. Happy to adjust if a maintainer wants a different validation lane.