Skip to content

Conversation

@ARYPROGRAMMER
Copy link
Contributor

Summary

Fixed a panic error ("index out of bounds") that occurred when Goose interacted with OpenAI-compatible servers (like mlx_lm.server or LM Studio). The error was triggered when these servers sent response chunks with an empty choices array, as the code accessed elements like response["choices"][0] and chunk.choices[0] without checking if the array was empty first. The issue occurs rarely and is now almost guranteed to not occur.


Root Cause Analysis (RCA, perhaps)

The panic was caused by unsafe array access in the OpenAI response parsing logic:

  • Streaming responses: The response_to_streaming_message() function directly accessed chunk.choices[0] without performing a bounds check.
  • Non-streaming responses: The response_to_message() function directly accessed response["choices"][0]["message"] without validating the array's existence or length.

While an empty choices array is valid according to the OpenAI specification, the existing code did not handle this case, leading to a crash.


Technical Solution

Defensive bounds checking was implemented in both response parsing pathways to prevent the crash:

  • Streaming Response Handler (response_to_streaming_message)

    • Added !chunk.choices.is_empty() guards before all accesses to choices[0].
    • Added !tool_chunk.choices.is_empty() checks within tool call processing loops.
    • Replaced direct access with safe pattern matching and bounds validation.
  • Non-Streaming Response Handler (response_to_message)

    • Added explicit validation using response.get("choices").and_then(|c| c.as_array()).
    • Included checks for empty arrays with descriptive error messages.
    • Switched from direct indexing to safe array access methods.

Type of Change

  • Feature
  • Bug fix
  • Refactor / Code quality
  • Performance improvement
  • Documentation
  • Tests
  • Security fix
  • Build / Release
  • Other

Testing

The fix was verified through the following steps:

  • The project was built successfully using cargo build.
  • Tests were conducted with an LM Studio server using both streaming and non-streaming requests.
  • Confirmed that responses with empty choices arrays no longer cause panics.
  • Verified that standard OpenAI API responses continue to work correctly.

Manual testing confirmed that Goose now gracefully handles empty choices arrays, ensuring compatibility with a wider range of OpenAI-compatible servers.

I can't actually test mlx server based but reverified with everything else. (needs apple chip)


Related Issues

Fixes #4276


Copy link
Collaborator

@DOsinga DOsinga left a comment

Choose a reason for hiding this comment

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

thanks for fixing this. if you want I saw that databricks has a similar way of doing things.


if choices.is_empty() {
return Err(anyhow!("Response contains empty choices array"));
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

this is probably good enough, but it feels more in line with the existing code to do something like:

let Some(original) = response
    .get("choices")
    .and_then(|c| c.get(0))
    .and_then(|m| m.get("message"))
else {
    return Ok(Message::with_content(Vec::new()));
};

i.e. if there is no content, we just return an empty message, same as if it is empty for other reasons below

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the review and the great suggestion, i've implemented your recommended change to use the more standard pattern that returns an empty message instead of an error when choices are missing or empty. This is indeed more consistent with how the rest of the codebase handles empty content cases.

lgtm now @DOsinga

Signed-off-by: Arya Pratap Singh <[email protected]>
@ARYPROGRAMMER
Copy link
Contributor Author

this is completed i think

if chunk.choices.is_empty() {
yield (None, usage)
} else if let Some(tool_calls) = &chunk.choices[0].delta.tool_calls {
} else if !chunk.choices.is_empty() && chunk.choices[0].delta.tool_calls.as_ref().is_some_and(|tc| !tc.is_empty()) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

here we aleady guard for empty though, don't we? on line 473?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah just for safety, should not impact

@ARYPROGRAMMER
Copy link
Contributor Author

just checking the build failure

Signed-off-by: Arya Pratap Singh <[email protected]>
@ARYPROGRAMMER
Copy link
Contributor Author

ARYPROGRAMMER commented Oct 18, 2025

@DOsinga please rerun ci pipelines for this, it should works now mostly

edit:

  1. removed redundant null check
  2. fixed message::new constructor

@DOsinga
Copy link
Collaborator

DOsinga commented Oct 18, 2025

there is still some of that checking going on I think

@ARYPROGRAMMER
Copy link
Contributor Author

there is still some of that checking going on I think

I think all have completed successfully now.

Thanks, glad I was able to fix this

@DOsinga DOsinga merged commit 3b04b2e into block:main Oct 18, 2025
10 checks passed
lifeizhou-ap added a commit that referenced this pull request Oct 19, 2025
* main: (32 commits)
  turn off WAL (#5203)
  Skip subagents for gemini (#5257)
  Revert "Standardize Session Name Attribute" (#5250)
  improve provider request logging a bit (#5236)
  Fix OpenAI empty choices panic (#5248)
  Revert "Rewrite extension management tools" (#5243)
  Standardize Session Name Attribute (#5085)
  Docs: Include step-by-step model configuration instructions for first… (#5239)
  Delete message visibility filter (#5216)
  delete flaky pricing integration tests (#5207)
  chore: upgrade most npm packages to latest (#5185)
  Release/1.11.0 (#5224)
  Rewrite extension management tools (#5057)
  fix: re-sync package-lock.json (#5235)
  docs: Hacktoberfest MCP youtube short entry to community-content.json (#5150)
  feat: add schedule button to recipe entries (#5217)
  Autocompact threshold UI cleanup (#5232)
  fix: correct schema for openai tools (#5229)
  Break compaction back into check_ and do_ compaction (#5212)
  fix: revert built app name to uppercase Goose (#5206)
  ...
michaelneale added a commit that referenced this pull request Oct 20, 2025
* main:
  Lifei/UI parameter input (#5222)
  turn off WAL (#5203)
  Skip subagents for gemini (#5257)
  Revert "Standardize Session Name Attribute" (#5250)
  improve provider request logging a bit (#5236)
  Fix OpenAI empty choices panic (#5248)
  Revert "Rewrite extension management tools" (#5243)
katzdave added a commit that referenced this pull request Oct 20, 2025
* 'main' of github.com:block/goose:
  docs: provide more clarity in tagging step of releases (#5269)
  docs: add GOOSE_DEBUG environment variable (#5265)
  Lifei/UI parameter input (#5222)
  turn off WAL (#5203)
  Skip subagents for gemini (#5257)
  Revert "Standardize Session Name Attribute" (#5250)
  improve provider request logging a bit (#5236)
  Fix OpenAI empty choices panic (#5248)
  Revert "Rewrite extension management tools" (#5243)
  Standardize Session Name Attribute (#5085)
  Docs: Include step-by-step model configuration instructions for first… (#5239)
  Delete message visibility filter (#5216)
  delete flaky pricing integration tests (#5207)
  chore: upgrade most npm packages to latest (#5185)
  Release/1.11.0 (#5224)
@ARYPROGRAMMER
Copy link
Contributor Author

@taniandjerry can you please add Hacktoberfest and Large label to this.

Thanks, I'll see this week's issues in a while

michaelneale added a commit that referenced this pull request Oct 21, 2025
* main: (40 commits)
  Fix extension manager resource deadlock (#5066)
  add new prompt for memory extension (#4945)
  feat: Stable system prompt and tool ordering for more cache hits (#5192)
  Remove gtag analytics error (#5268)
  Feat/smart task organizer recipe (#5032)
  docs: `goose recipe open` command (#5264)
  docs: provide more clarity in tagging step of releases (#5269)
  docs: add GOOSE_DEBUG environment variable (#5265)
  Lifei/UI parameter input (#5222)
  turn off WAL (#5203)
  Skip subagents for gemini (#5257)
  Revert "Standardize Session Name Attribute" (#5250)
  improve provider request logging a bit (#5236)
  Fix OpenAI empty choices panic (#5248)
  Revert "Rewrite extension management tools" (#5243)
  Standardize Session Name Attribute (#5085)
  Docs: Include step-by-step model configuration instructions for first… (#5239)
  Delete message visibility filter (#5216)
  delete flaky pricing integration tests (#5207)
  chore: upgrade most npm packages to latest (#5185)
  ...
michaelneale added a commit that referenced this pull request Oct 21, 2025
* main:
  Lifei/UI parameter input (#5222)
  turn off WAL (#5203)
  Skip subagents for gemini (#5257)
  Revert "Standardize Session Name Attribute" (#5250)
  improve provider request logging a bit (#5236)
  Fix OpenAI empty choices panic (#5248)
@taniandjerry taniandjerry added hacktoberfest Issues awarding points for Hacktoberfest 2025! large Weight label for Hacktoberfest 2025 issues hacktoberfest-accepted labels Oct 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

hacktoberfest Issues awarding points for Hacktoberfest 2025! hacktoberfest-accepted large Weight label for Hacktoberfest 2025 issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Panicked with OpenAI-compatible mlx_lm.server

3 participants