Skip to content

Fix apps extension: coerce string arguments from inner LLM responses#8030

Merged
DOsinga merged 2 commits intomainfrom
fix/apps-tool-argument-coercion
Mar 20, 2026
Merged

Fix apps extension: coerce string arguments from inner LLM responses#8030
DOsinga merged 2 commits intomainfrom
fix/apps-tool-argument-coercion

Conversation

@DOsinga
Copy link
Copy Markdown
Collaborator

@DOsinga DOsinga commented Mar 20, 2026

Problem

When the apps extension calls an inner LLM to generate app content (e.g. create_app_content, update_app_content), the LLM sometimes returns numeric values as strings (e.g. "600" instead of 600 for window width). This caused serde deserialization to fail with:

invalid type: string "600", expected u32

Root Cause

Normal (outer) tool calls already go through coerce_tool_arguments() in reply_parts.rs via categorize_tool_requests, which coerces string values to their correct types based on the tool's JSON Schema. The inner LLM tool calls in the apps extension bypassed this — extract_tool_response() deserialized directly from the raw response without coercion.

Fix

Reuse the existing coerce_tool_arguments() in extract_tool_response() so inner LLM responses get the same type coercion as normal tool calls.

Closes #8028

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a0da2dd303

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Ok(event) => {
if event.seq <= replay_max_seq { continue; }
let json = serde_json::to_string(&event.event).unwrap_or_default();
if tx.send(format!("data: {}\n\n", json)).await.is_err() { return; }
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Cancel reply task when inline SSE client disconnects

When the /reply SSE writer fails to send (for example, a client aborts the HTTP stream), this branch returns without canceling the request token, so the spawned agent task keeps running in the background and can continue consuming model/tool work after the user has disconnected. The old inline path canceled on send failure; this regression affects clients that rely on dropping the stream to stop work.

Useful? React with 👍 / 👎.

Comment on lines +522 to +524
let (replay, replay_max_seq, mut live_rx) = match bus.subscribe(None).await {
Ok(result) => result,
Err(_) => return,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Restrict /reply replay to the active request

This subscribes with bus.subscribe(None) and replays all buffered session events before streaming live ones, but /reply is a single-request stream and does not filter by request_id. On subsequent calls in the same session, clients can receive stale Message/Finish/Error events from earlier requests before the new reply, which can corrupt output handling. Replay/live forwarding should be scoped to the generated request_id for this invocation.

Useful? React with 👍 / 👎.

When the apps extension calls an inner LLM to generate app content, the
LLM sometimes returns numeric values as strings (e.g., "600" instead
of 600 for window width). This caused serde deserialization to fail with
'invalid type: string "600", expected u32'.

Normal tool calls already go through coerce_tool_arguments() in
reply_parts.rs (via categorize_tool_requests), which handles this
string-to-number coercion. The inner LLM tool calls in the apps
extension bypassed this by deserializing directly from the raw response.

Fix: reuse the existing coerce_tool_arguments() in extract_tool_response()
so inner LLM responses get the same type coercion as normal tool calls.

Closes #8028

Signed-off-by: Douwe Osinga <[email protected]>
@DOsinga DOsinga force-pushed the fix/apps-tool-argument-coercion branch from a0da2dd to e499dc4 Compare March 20, 2026 11:46
@DOsinga DOsinga requested a review from alexhancock March 20, 2026 11:47
@DOsinga DOsinga added this pull request to the merge queue Mar 20, 2026
Merged via the queue into main with commit 9f2b3cc Mar 20, 2026
22 of 23 checks passed
@DOsinga DOsinga deleted the fix/apps-tool-argument-coercion branch March 20, 2026 14:16
elijahsgh pushed a commit to elijahsgh/goose that referenced this pull request Mar 21, 2026
elijahsgh pushed a commit to elijahsgh/goose that referenced this pull request Mar 21, 2026
wpfleger96 added a commit that referenced this pull request Mar 23, 2026
* origin/main: (62 commits)
  Tweak the release process: no more merge to main (#7994)
  fix: gemini models via databricks (#8042)
  feat(apps): Pass toolInfo to MCP Apps via hostContext (#7506)
  fix: remove configured marker when deleting oauth provider configuration (#7887)
  docs: add vmware-aiops MCP extension documentation (#8055)
  Show setup instructions for ACP providers in settings modal (#8065)
  deps: replace sigstore-verification with sigstore-verify to kill vulns (#8064)
  feat(acp): add session/set_config and stabilize list, delete and close (#7984)
  docs: Correct `gosoe` typo to `goose` (#8062)
  fix: use default provider and model when provider in session no longer exists (#8035)
  feat: add GOOSE_SHELL env var to configure preferred shell (#7909)
  fix(desktop): fullscreen header bar + always-visible close controls (#8033)
  docs: add Claude Code approve mode permission routing documentation (#7949)
  chatgpt_codex: Support reasoning and gpt-5.4 (#7941)
  refactor(anthropic): fix N+1 thinking message storage issue (#7958)
  fix: handle mid-stream error events in OpenAI SSE streaming (#8031)
  Fix apps extension: coerce string arguments from inner LLM responses (#8030)
  feat: ability to expand sidebar to see chats names  (#7816)
  Fix config for GOOSE_MAX_BACKGROUND_TASKS (#7940)
  set MACOSX_DEPLOYMENT_TARGET=12.0 (#7947)
  ...
wpfleger96 added a commit that referenced this pull request Mar 23, 2026
…pstream

* wpfleger/socket-support: (62 commits)
  Tweak the release process: no more merge to main (#7994)
  fix: gemini models via databricks (#8042)
  feat(apps): Pass toolInfo to MCP Apps via hostContext (#7506)
  fix: remove configured marker when deleting oauth provider configuration (#7887)
  docs: add vmware-aiops MCP extension documentation (#8055)
  Show setup instructions for ACP providers in settings modal (#8065)
  deps: replace sigstore-verification with sigstore-verify to kill vulns (#8064)
  feat(acp): add session/set_config and stabilize list, delete and close (#7984)
  docs: Correct `gosoe` typo to `goose` (#8062)
  fix: use default provider and model when provider in session no longer exists (#8035)
  feat: add GOOSE_SHELL env var to configure preferred shell (#7909)
  fix(desktop): fullscreen header bar + always-visible close controls (#8033)
  docs: add Claude Code approve mode permission routing documentation (#7949)
  chatgpt_codex: Support reasoning and gpt-5.4 (#7941)
  refactor(anthropic): fix N+1 thinking message storage issue (#7958)
  fix: handle mid-stream error events in OpenAI SSE streaming (#8031)
  Fix apps extension: coerce string arguments from inner LLM responses (#8030)
  feat: ability to expand sidebar to see chats names  (#7816)
  Fix config for GOOSE_MAX_BACKGROUND_TASKS (#7940)
  set MACOSX_DEPLOYMENT_TARGET=12.0 (#7947)
  ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

apps__create_app - Invalid type error when creating app

2 participants