Skip to content

mcp tool call with error handling - #5894

Merged
akshaydeo merged 8 commits into
devfrom
08-06-mcp_tool_call_with_error_handling
Aug 6, 2026
Merged

akshaydeo merged 8 commits into
devfrom
08-06-mcp_tool_call_with_error_handling

Conversation

@akshaydeo

@akshaydeo akshaydeo commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Failed MCP tool executions were being replayed to the model as successful results. The is_error / IsError marker was either never set, silently dropped during JSON unmarshalling, or lost when converting between the chat completions and Responses surfaces. This PR closes all three gaps so that Anthropic tool_result.is_error, Bedrock toolResult.status, and the Responses surface status: "incomplete" all reflect the actual outcome of a tool call.

Changes

  • Agent loop (mcp/agent.go): createToolResultMessage now sets IsError: true on the returned ChatToolMessage when the execution error is non-nil, so a tool call that bifrost itself watched fail is no longer reported to the model as a success.
  • MCP tool manager (mcp/toolmanager.go, mcp/utils.go): createToolResponseMessage gains an isError bool parameter. executeToolInternal passes toolResponse.IsError through, carrying the MCP protocol's own failure signal (mcp.CallToolResult.IsError) which was previously discarded.
  • CodeMode (mcp/codemode/starlark/): All call sites of createToolResponseMessage are updated to pass an explicit isError flag. Lookup failures (unknown server, unknown tool, ambiguous filename), sandbox execution failures, and MCP protocol-level errors are all marked true; successful responses and informational listings are marked false.
  • Unmarshal gate (schemas/chatcompletions.go): The ChatMessage.UnmarshalJSON reattach gate previously keyed solely on ToolCallID, which silently dropped ChatToolMessage — and with it IsError — for any tool message that carried the error marker without a tool_call_id. The gate now also triggers on IsError != nil.
  • Chat ↔ Responses mux (schemas/mux.go): ToResponsesMessages maps IsError: true to Status: "incomplete" (the Responses surface equivalent already read back as is_error by the Anthropic Responses converter). ToChatMessages maps both ResponsesToolMessage.Error (non-empty string) and Status: "incomplete" back to IsError: true, so the marker survives a full chat → Responses → chat round trip.
  • Tests: New test files cover the agent-loop marker (mcp/toolerrormarker_test.go), the CodeMode marker (mcp/codemode/starlark/toolerrormarker_test.go), the unmarshal gate edge case (schemas/toolmessageiserror_test.go), and both directions of the mux conversion including a round-trip regression (schemas/toolerrormux_test.go).

Type of change

  • Bug fix

Affected areas

  • Core (Go)
  • Providers/Integrations

How to test

go test ./core/...

Key test targets:

  • core/mcpTestCreateToolResultMessageMarksError, TestCreateToolResponseMessageMarksError
  • core/mcp/codemode/starlarkTestCreateToolResponseMessageMarksError
  • core/schemasTestChatMessageIsErrorWithoutToolCallID, TestChatToResponsesCarriesToolError, TestResponsesToChatCarriesToolError, TestToolErrorSurvivesSurfaceRoundTrip

Breaking changes

  • Yes
  • No

Related issues

Closes #5890

Security considerations

None. Changes are confined to how tool result metadata is propagated between internal surfaces and providers.

Checklist

  • I read docs/contributing/README.md and followed the guidelines
  • I added/updated tests where appropriate
  • I updated documentation where needed
  • I verified builds succeed (Go and UI)
  • I verified the CI pipeline passes locally if applicable

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes

    • Tool execution failures are now preserved across Chat and Responses formats.
    • Failed MCP and provider tool results are no longer treated as successful responses.
    • Error-only tool messages are retained even without a tool call ID.
    • Tool error status remains accurate through conversions and round trips.
  • Tests

    • Added coverage for MCP, provider, agent, and cross-format tool error handling.

Walkthrough

MCP execution and protocol failures now set ChatToolMessage.IsError. CodeMode responses pass explicit error status. Chat and Responses conversions preserve tool errors, including messages without a tool_call_id. Bedrock conversion tests cover failed and successful tool results.

Changes

Tool error propagation

Layer / File(s) Summary
MCP and CodeMode error markers
core/mcp/agent.go, core/mcp/utils.go, core/mcp/toolmanager.go, core/mcp/codemode/starlark/..., core/mcp/*toolerrormarker_test.go
Tool execution failures and MCP IsError results now set ChatToolMessage.IsError. CodeMode lookup, sandbox, and nested MCP failures pass explicit error status.
Chat, Responses, and Bedrock conversion
core/schemas/chatcompletions.go, core/schemas/mux.go, core/schemas/toolerrormux_test.go, core/schemas/toolmessageiserror_test.go, core/providers/bedrock/toolerroringress_test.go
Chat tool errors convert to Responses incomplete status. Responses error strings and incomplete status convert back to IsError=true. Error-only messages without tool_call_id are preserved. Bedrock tests cover failed and successful conversions.
Changelog
core/changelog.md
The changelog records the tool-result error propagation changes.

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

Sequence Diagram(s)

sequenceDiagram
  participant MCPExecution
  participant ChatToolMessage
  participant ChatToResponses
  participant ResponsesToChat
  MCPExecution->>ChatToolMessage: Set IsError for failed execution
  ChatToolMessage->>ChatToResponses: Convert IsError=true
  ChatToResponses->>ResponsesToChat: Preserve incomplete status
  ResponsesToChat->>ChatToolMessage: Restore IsError=true
Loading

Possibly related PRs

  • maximhq/bifrost#5450: Extends the ChatToolMessage.IsError propagation introduced by this change with provider wire mappings.
  • maximhq/bifrost#5573: Modifies related nested Starlark MCP error propagation.

Suggested reviewers: pratham-mishra04

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes satisfy #5890 by preserving tool-error markers through execution, unmarshalling, and Chat-to-Responses conversions, with regression tests.
Out of Scope Changes check ✅ Passed The production changes, tests, and changelog entries remain focused on MCP tool-error propagation and the requirements of #5890.
Docstring Coverage ✅ Passed Docstring coverage is 94.12% which is sufficient. The required threshold is 80.00%.
Title check ✅ Passed The title clearly identifies MCP tool-call error handling, which matches the primary change.
Description check ✅ Passed The description explains the problem, changes, tests, affected areas, breaking status, issue, security impact, and checklist.
✨ 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 08-06-mcp_tool_call_with_error_handling

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

@akshaydeo akshaydeo mentioned this pull request Aug 6, 2026
18 tasks
@akshaydeo
akshaydeo marked this pull request as ready for review August 6, 2026 09:12
@coderabbitai
coderabbitai Bot requested a review from Pratham-Mishra04 August 6, 2026 09:13

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@core/mcp/codemode/starlark/executecode.go`:
- Around line 199-201: Update the result.Errors != nil branch in the sandbox
execution flow to set executionSuccess = false before createToolResponseMessage
is called, ensuring failed executions are marked as tool failures. Add a
regression test covering an execution result with non-nil Errors and verify the
response is flagged as unsuccessful.
- Around line 556-560: The extractResultFromChatMessage path must propagate
protocol-level tool failures by checking ChatToolMessage.IsError and returning
an error when it is set, including for responses without an “Error: ” text
prefix. Preserve the existing successful result handling when the flag is false.

In `@core/schemas/mux.go`:
- Around line 851-858: Update the Responses-to-Chat allocation guard in
core/schemas/mux.go#L851-L858 to create ChatToolMessage when CallID is present
or when ResponsesToolMessage.Error is non-empty or Status is "incomplete",
preserving IsError for ID-less failures. Add corresponding error-string and
incomplete-status fixtures without CallID in
core/schemas/toolerrormux_test.go#L51-L114, asserting IsError is true for both.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 996f9d94-1a15-459e-bcfb-59e193eafdf7

📥 Commits

Reviewing files that changed from the base of the PR and between cee6003 and e68d6ff.

📒 Files selected for processing (15)
  • core/changelog.md
  • core/mcp/agent.go
  • core/mcp/codemode/starlark/executecode.go
  • core/mcp/codemode/starlark/getdocs.go
  • core/mcp/codemode/starlark/listfiles.go
  • core/mcp/codemode/starlark/readfile.go
  • core/mcp/codemode/starlark/toolerrormarker_test.go
  • core/mcp/codemode/starlark/utils.go
  • core/mcp/toolerrormarker_test.go
  • core/mcp/toolmanager.go
  • core/mcp/utils.go
  • core/schemas/chatcompletions.go
  • core/schemas/mux.go
  • core/schemas/toolerrormux_test.go
  • core/schemas/toolmessageiserror_test.go

Comment thread core/mcp/codemode/starlark/executecode.go
Comment thread core/mcp/codemode/starlark/executecode.go
Comment thread core/schemas/mux.go
@akshaydeo
akshaydeo force-pushed the 08-06-mcp_tool_call_with_error_handling branch from e68d6ff to 413be75 Compare August 6, 2026 09:22
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
core/mcp/toolerrormarker_test.go (1)

10-18: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use schemas.Ptr for fixture pointers.

Replace &id and &name with schemas.Ptr(id) and schemas.Ptr(name). This follows the repository pointer-helper convention for simple values.

Based on learnings, “prefer using bifrost.Ptr() to create pointers instead of the address operator (&) … including test utilities.”

Proposed change
-		ID: &id,
+		ID: schemas.Ptr(id),
 		Function: schemas.ChatAssistantMessageToolCallFunction{
-			Name:      &name,
+			Name:      schemas.Ptr(name),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/mcp/toolerrormarker_test.go` around lines 10 - 18, Update the
testToolCall fixture to use schemas.Ptr(id) for ID and schemas.Ptr(name) for
Function.Name instead of address operators, preserving the existing
ChatAssistantMessageToolCall values and repository pointer-helper convention.

Source: Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@core/providers/bedrock/toolerroringress_test.go`:
- Around line 110-115: Update the success-conversion assertions in the test to
first locate a non-nil ChatToolMessage within chatReq.Input and fail if none
exists, then assert that the located message’s IsError field is nil. Ensure the
test distinguishes a missing successful tool message from one that is present
but incorrectly marked as an error.

---

Nitpick comments:
In `@core/mcp/toolerrormarker_test.go`:
- Around line 10-18: Update the testToolCall fixture to use schemas.Ptr(id) for
ID and schemas.Ptr(name) for Function.Name instead of address operators,
preserving the existing ChatAssistantMessageToolCall values and repository
pointer-helper convention.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: c480247c-da45-454b-8388-b79b8f553fee

📥 Commits

Reviewing files that changed from the base of the PR and between cee6003 and 413be75.

📒 Files selected for processing (16)
  • core/changelog.md
  • core/mcp/agent.go
  • core/mcp/codemode/starlark/executecode.go
  • core/mcp/codemode/starlark/getdocs.go
  • core/mcp/codemode/starlark/listfiles.go
  • core/mcp/codemode/starlark/readfile.go
  • core/mcp/codemode/starlark/toolerrormarker_test.go
  • core/mcp/codemode/starlark/utils.go
  • core/mcp/toolerrormarker_test.go
  • core/mcp/toolmanager.go
  • core/mcp/utils.go
  • core/providers/bedrock/toolerroringress_test.go
  • core/schemas/chatcompletions.go
  • core/schemas/mux.go
  • core/schemas/toolerrormux_test.go
  • core/schemas/toolmessageiserror_test.go
🚧 Files skipped from review as they are similar to previous changes (14)
  • core/mcp/toolmanager.go
  • core/mcp/codemode/starlark/listfiles.go
  • core/mcp/codemode/starlark/toolerrormarker_test.go
  • core/mcp/codemode/starlark/getdocs.go
  • core/schemas/mux.go
  • core/changelog.md
  • core/mcp/agent.go
  • core/mcp/codemode/starlark/executecode.go
  • core/schemas/toolmessageiserror_test.go
  • core/schemas/chatcompletions.go
  • core/mcp/codemode/starlark/readfile.go
  • core/schemas/toolerrormux_test.go
  • core/mcp/codemode/starlark/utils.go
  • core/mcp/utils.go

Comment thread core/providers/bedrock/toolerroringress_test.go
@akshaydeo akshaydeo mentioned this pull request Aug 6, 2026
9 tasks
@akshaydeo
akshaydeo force-pushed the 08-06-mcp_tool_call_with_error_handling branch from 413be75 to 266a3c4 Compare August 6, 2026 17:18
@akshaydeo
akshaydeo force-pushed the 08-06-fail_soft_on_invalid_encrypted_content_by_stripping_replayed_reasoning_and_retrying_once branch from cee6003 to 26d84d0 Compare August 6, 2026 17:18
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@akshaydeo akshaydeo mentioned this pull request Aug 6, 2026
18 tasks
coderabbitai[bot]
coderabbitai Bot previously approved these changes Aug 6, 2026
@akshaydeo
akshaydeo force-pushed the 08-06-fail_soft_on_invalid_encrypted_content_by_stripping_replayed_reasoning_and_retrying_once branch from 26d84d0 to e615503 Compare August 6, 2026 18:01
@akshaydeo
akshaydeo force-pushed the 08-06-mcp_tool_call_with_error_handling branch from 266a3c4 to a8ee2d2 Compare August 6, 2026 18:01
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@akshaydeo
akshaydeo force-pushed the 08-06-mcp_tool_call_with_error_handling branch from a8ee2d2 to c8bbad0 Compare August 6, 2026 19:38
@akshaydeo
akshaydeo force-pushed the 08-06-fail_soft_on_invalid_encrypted_content_by_stripping_replayed_reasoning_and_retrying_once branch from e615503 to e8604f9 Compare August 6, 2026 19:38
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@akshaydeo
akshaydeo force-pushed the 08-06-fail_soft_on_invalid_encrypted_content_by_stripping_replayed_reasoning_and_retrying_once branch from e8604f9 to e8b5de0 Compare August 6, 2026 19:57
@akshaydeo
akshaydeo force-pushed the 08-06-mcp_tool_call_with_error_handling branch from c8bbad0 to 0e91527 Compare August 6, 2026 19:57
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

akshaydeo commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

Merge activity

  • Aug 6, 7:58 PM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Aug 6, 8:05 PM UTC: @akshaydeo merged this pull request with Graphite.

@akshaydeo
akshaydeo changed the base branch from 08-06-fail_soft_on_invalid_encrypted_content_by_stripping_replayed_reasoning_and_retrying_once to graphite-base/5894 August 6, 2026 20:04
@akshaydeo
akshaydeo changed the base branch from graphite-base/5894 to dev August 6, 2026 20:05
@akshaydeo
akshaydeo dismissed coderabbitai[bot]’s stale review August 6, 2026 20:05

The base branch was changed.

@akshaydeo
akshaydeo requested a review from a team as a code owner August 6, 2026 20:05
@akshaydeo
akshaydeo merged commit 06eca74 into dev Aug 6, 2026
6 checks passed
@akshaydeo
akshaydeo deleted the 08-06-mcp_tool_call_with_error_handling branch August 6, 2026 20:05
akshaydeo added a commit that referenced this pull request Aug 7, 2026
## Summary

Failed MCP tool executions were being replayed to the model as successful results. The `is_error` / `IsError` marker was either never set, silently dropped during JSON unmarshalling, or lost when converting between the chat completions and Responses surfaces. This PR closes all three gaps so that Anthropic `tool_result.is_error`, Bedrock `toolResult.status`, and the Responses surface `status: "incomplete"` all reflect the actual outcome of a tool call.

## Changes

- **Agent loop (`mcp/agent.go`):** `createToolResultMessage` now sets `IsError: true` on the returned `ChatToolMessage` when the execution error is non-nil, so a tool call that bifrost itself watched fail is no longer reported to the model as a success.
- **MCP tool manager (`mcp/toolmanager.go`, `mcp/utils.go`):** `createToolResponseMessage` gains an `isError bool` parameter. `executeToolInternal` passes `toolResponse.IsError` through, carrying the MCP protocol's own failure signal (`mcp.CallToolResult.IsError`) which was previously discarded.
- **CodeMode (`mcp/codemode/starlark/`):** All call sites of `createToolResponseMessage` are updated to pass an explicit `isError` flag. Lookup failures (unknown server, unknown tool, ambiguous filename), sandbox execution failures, and MCP protocol-level errors are all marked `true`; successful responses and informational listings are marked `false`.
- **Unmarshal gate (`schemas/chatcompletions.go`):** The `ChatMessage.UnmarshalJSON` reattach gate previously keyed solely on `ToolCallID`, which silently dropped `ChatToolMessage` — and with it `IsError` — for any tool message that carried the error marker without a `tool_call_id`. The gate now also triggers on `IsError != nil`.
- **Chat ↔ Responses mux (`schemas/mux.go`):** `ToResponsesMessages` maps `IsError: true` to `Status: "incomplete"` (the Responses surface equivalent already read back as `is_error` by the Anthropic Responses converter). `ToChatMessages` maps both `ResponsesToolMessage.Error` (non-empty string) and `Status: "incomplete"` back to `IsError: true`, so the marker survives a full chat → Responses → chat round trip.
- **Tests:** New test files cover the agent-loop marker (`mcp/toolerrormarker_test.go`), the CodeMode marker (`mcp/codemode/starlark/toolerrormarker_test.go`), the unmarshal gate edge case (`schemas/toolmessageiserror_test.go`), and both directions of the mux conversion including a round-trip regression (`schemas/toolerrormux_test.go`).

## Type of change

- [x] Bug fix

## Affected areas

- [x] Core (Go)
- [x] Providers/Integrations

## How to test

```sh
go test ./core/...
```

Key test targets:
- `core/mcp` — `TestCreateToolResultMessageMarksError`, `TestCreateToolResponseMessageMarksError`
- `core/mcp/codemode/starlark` — `TestCreateToolResponseMessageMarksError`
- `core/schemas` — `TestChatMessageIsErrorWithoutToolCallID`, `TestChatToResponsesCarriesToolError`, `TestResponsesToChatCarriesToolError`, `TestToolErrorSurvivesSurfaceRoundTrip`

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

Closes #5890

## Security considerations

None. Changes are confined to how tool result metadata is propagated between internal surfaces and providers.

## Checklist

- [x] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [x] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [x] I verified the CI pipeline passes locally if applicable
atharvamhaske pushed a commit to atharvamhaske/bifrost that referenced this pull request Aug 13, 2026
## Summary

Failed MCP tool executions were being replayed to the model as successful results. The `is_error` / `IsError` marker was either never set, silently dropped during JSON unmarshalling, or lost when converting between the chat completions and Responses surfaces. This PR closes all three gaps so that Anthropic `tool_result.is_error`, Bedrock `toolResult.status`, and the Responses surface `status: "incomplete"` all reflect the actual outcome of a tool call.

## Changes

- **Agent loop (`mcp/agent.go`):** `createToolResultMessage` now sets `IsError: true` on the returned `ChatToolMessage` when the execution error is non-nil, so a tool call that bifrost itself watched fail is no longer reported to the model as a success.
- **MCP tool manager (`mcp/toolmanager.go`, `mcp/utils.go`):** `createToolResponseMessage` gains an `isError bool` parameter. `executeToolInternal` passes `toolResponse.IsError` through, carrying the MCP protocol's own failure signal (`mcp.CallToolResult.IsError`) which was previously discarded.
- **CodeMode (`mcp/codemode/starlark/`):** All call sites of `createToolResponseMessage` are updated to pass an explicit `isError` flag. Lookup failures (unknown server, unknown tool, ambiguous filename), sandbox execution failures, and MCP protocol-level errors are all marked `true`; successful responses and informational listings are marked `false`.
- **Unmarshal gate (`schemas/chatcompletions.go`):** The `ChatMessage.UnmarshalJSON` reattach gate previously keyed solely on `ToolCallID`, which silently dropped `ChatToolMessage` — and with it `IsError` — for any tool message that carried the error marker without a `tool_call_id`. The gate now also triggers on `IsError != nil`.
- **Chat ↔ Responses mux (`schemas/mux.go`):** `ToResponsesMessages` maps `IsError: true` to `Status: "incomplete"` (the Responses surface equivalent already read back as `is_error` by the Anthropic Responses converter). `ToChatMessages` maps both `ResponsesToolMessage.Error` (non-empty string) and `Status: "incomplete"` back to `IsError: true`, so the marker survives a full chat → Responses → chat round trip.
- **Tests:** New test files cover the agent-loop marker (`mcp/toolerrormarker_test.go`), the CodeMode marker (`mcp/codemode/starlark/toolerrormarker_test.go`), the unmarshal gate edge case (`schemas/toolmessageiserror_test.go`), and both directions of the mux conversion including a round-trip regression (`schemas/toolerrormux_test.go`).

## Type of change

- [x] Bug fix

## Affected areas

- [x] Core (Go)
- [x] Providers/Integrations

## How to test

```sh
go test ./core/...
```

Key test targets:
- `core/mcp` — `TestCreateToolResultMessageMarksError`, `TestCreateToolResponseMessageMarksError`
- `core/mcp/codemode/starlark` — `TestCreateToolResponseMessageMarksError`
- `core/schemas` — `TestChatMessageIsErrorWithoutToolCallID`, `TestChatToResponsesCarriesToolError`, `TestResponsesToChatCarriesToolError`, `TestToolErrorSurvivesSurfaceRoundTrip`

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

Closes maximhq#5890

## Security considerations

None. Changes are confined to how tool result metadata is propagated between internal surfaces and providers.

## Checklist

- [x] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [x] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [x] I verified the CI pipeline passes locally if applicable
akhsaul pushed a commit to akhsaul/bifrost that referenced this pull request Aug 27, 2026
## Summary

Failed MCP tool executions were being replayed to the model as successful results. The `is_error` / `IsError` marker was either never set, silently dropped during JSON unmarshalling, or lost when converting between the chat completions and Responses surfaces. This PR closes all three gaps so that Anthropic `tool_result.is_error`, Bedrock `toolResult.status`, and the Responses surface `status: "incomplete"` all reflect the actual outcome of a tool call.

## Changes

- **Agent loop (`mcp/agent.go`):** `createToolResultMessage` now sets `IsError: true` on the returned `ChatToolMessage` when the execution error is non-nil, so a tool call that bifrost itself watched fail is no longer reported to the model as a success.
- **MCP tool manager (`mcp/toolmanager.go`, `mcp/utils.go`):** `createToolResponseMessage` gains an `isError bool` parameter. `executeToolInternal` passes `toolResponse.IsError` through, carrying the MCP protocol's own failure signal (`mcp.CallToolResult.IsError`) which was previously discarded.
- **CodeMode (`mcp/codemode/starlark/`):** All call sites of `createToolResponseMessage` are updated to pass an explicit `isError` flag. Lookup failures (unknown server, unknown tool, ambiguous filename), sandbox execution failures, and MCP protocol-level errors are all marked `true`; successful responses and informational listings are marked `false`.
- **Unmarshal gate (`schemas/chatcompletions.go`):** The `ChatMessage.UnmarshalJSON` reattach gate previously keyed solely on `ToolCallID`, which silently dropped `ChatToolMessage` — and with it `IsError` — for any tool message that carried the error marker without a `tool_call_id`. The gate now also triggers on `IsError != nil`.
- **Chat ↔ Responses mux (`schemas/mux.go`):** `ToResponsesMessages` maps `IsError: true` to `Status: "incomplete"` (the Responses surface equivalent already read back as `is_error` by the Anthropic Responses converter). `ToChatMessages` maps both `ResponsesToolMessage.Error` (non-empty string) and `Status: "incomplete"` back to `IsError: true`, so the marker survives a full chat → Responses → chat round trip.
- **Tests:** New test files cover the agent-loop marker (`mcp/toolerrormarker_test.go`), the CodeMode marker (`mcp/codemode/starlark/toolerrormarker_test.go`), the unmarshal gate edge case (`schemas/toolmessageiserror_test.go`), and both directions of the mux conversion including a round-trip regression (`schemas/toolerrormux_test.go`).

## Type of change

- [x] Bug fix

## Affected areas

- [x] Core (Go)
- [x] Providers/Integrations

## How to test

```sh
go test ./core/...
```

Key test targets:
- `core/mcp` — `TestCreateToolResultMessageMarksError`, `TestCreateToolResponseMessageMarksError`
- `core/mcp/codemode/starlark` — `TestCreateToolResponseMessageMarksError`
- `core/schemas` — `TestChatMessageIsErrorWithoutToolCallID`, `TestChatToResponsesCarriesToolError`, `TestResponsesToChatCarriesToolError`, `TestToolErrorSurvivesSurfaceRoundTrip`

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

Closes maximhq#5890

## Security considerations

None. Changes are confined to how tool result metadata is propagated between internal surfaces and providers.

## Checklist

- [x] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [x] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [x] I verified the CI pipeline passes locally if applicable
occcat pushed a commit to occcat/bifrost that referenced this pull request Sep 2, 2026
## Summary

Failed MCP tool executions were being replayed to the model as successful results. The `is_error` / `IsError` marker was either never set, silently dropped during JSON unmarshalling, or lost when converting between the chat completions and Responses surfaces. This PR closes all three gaps so that Anthropic `tool_result.is_error`, Bedrock `toolResult.status`, and the Responses surface `status: "incomplete"` all reflect the actual outcome of a tool call.

## Changes

- **Agent loop (`mcp/agent.go`):** `createToolResultMessage` now sets `IsError: true` on the returned `ChatToolMessage` when the execution error is non-nil, so a tool call that bifrost itself watched fail is no longer reported to the model as a success.
- **MCP tool manager (`mcp/toolmanager.go`, `mcp/utils.go`):** `createToolResponseMessage` gains an `isError bool` parameter. `executeToolInternal` passes `toolResponse.IsError` through, carrying the MCP protocol's own failure signal (`mcp.CallToolResult.IsError`) which was previously discarded.
- **CodeMode (`mcp/codemode/starlark/`):** All call sites of `createToolResponseMessage` are updated to pass an explicit `isError` flag. Lookup failures (unknown server, unknown tool, ambiguous filename), sandbox execution failures, and MCP protocol-level errors are all marked `true`; successful responses and informational listings are marked `false`.
- **Unmarshal gate (`schemas/chatcompletions.go`):** The `ChatMessage.UnmarshalJSON` reattach gate previously keyed solely on `ToolCallID`, which silently dropped `ChatToolMessage` — and with it `IsError` — for any tool message that carried the error marker without a `tool_call_id`. The gate now also triggers on `IsError != nil`.
- **Chat ↔ Responses mux (`schemas/mux.go`):** `ToResponsesMessages` maps `IsError: true` to `Status: "incomplete"` (the Responses surface equivalent already read back as `is_error` by the Anthropic Responses converter). `ToChatMessages` maps both `ResponsesToolMessage.Error` (non-empty string) and `Status: "incomplete"` back to `IsError: true`, so the marker survives a full chat → Responses → chat round trip.
- **Tests:** New test files cover the agent-loop marker (`mcp/toolerrormarker_test.go`), the CodeMode marker (`mcp/codemode/starlark/toolerrormarker_test.go`), the unmarshal gate edge case (`schemas/toolmessageiserror_test.go`), and both directions of the mux conversion including a round-trip regression (`schemas/toolerrormux_test.go`).

## Type of change

- [x] Bug fix

## Affected areas

- [x] Core (Go)
- [x] Providers/Integrations

## How to test

```sh
go test ./core/...
```

Key test targets:
- `core/mcp` — `TestCreateToolResultMessageMarksError`, `TestCreateToolResponseMessageMarksError`
- `core/mcp/codemode/starlark` — `TestCreateToolResponseMessageMarksError`
- `core/schemas` — `TestChatMessageIsErrorWithoutToolCallID`, `TestChatToResponsesCarriesToolError`, `TestResponsesToChatCarriesToolError`, `TestToolErrorSurvivesSurfaceRoundTrip`

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

Closes maximhq#5890

## Security considerations

None. Changes are confined to how tool result metadata is propagated between internal surfaces and providers.

## Checklist

- [x] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [x] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [x] I verified the CI pipeline passes locally if applicable
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.

[Bug]: chat completions surface drops tool_result is_error, so failed tool calls replay to the model as successful

1 participant