Skip to content

fix: strip thinking block if signature is empty - #4079

Merged
akshaydeo merged 2 commits into
devfrom
06-05-fix_strip_thinking_block_if_signature_is_empty
Jun 5, 2026
Merged

fix: strip thinking block if signature is empty#4079
akshaydeo merged 2 commits into
devfrom
06-05-fix_strip_thinking_block_if_signature_is_empty

Conversation

@TejasGhatte

@TejasGhatte TejasGhatte commented Jun 5, 2026

Copy link
Copy Markdown
Collaborator

Summary

StripEmptyThinkingBlocks previously only stripped thinking blocks where the thinking field was empty. This missed a critical cross-provider case: when a thinking block originates from a non-Anthropic upstream (e.g. OpenAI, Gemini), it will have a non-empty thinking field but an empty signature field. Anthropic always emits a signature; OpenAI never does. Replaying such blocks to Anthropic causes a 400 rejection. This PR extends the stripping logic to also remove thinking blocks with an empty signature, and moves the StripEmptyThinkingBlocks call to after all other request body transformations so it applies to the fully-built body.

Changes

  • StripEmptyThinkingBlocks now strips thinking blocks where either thinking or signature is empty, covering both the original Anthropic-empty-thinking case and the cross-provider replay case where a signature is absent.
  • The StripEmptyThinkingBlocks call was moved from inside the initial marshal block to after all subsequent transformations (tool version remapping, etc.), ensuring it operates on the final request body.
  • Tests were added covering: empty thinking + empty signature, non-empty thinking but empty signature (OpenAI/Gemini replay), valid Anthropic blocks, mixed valid/invalid blocks, redacted_thinking blocks (unaffected), multi-message bodies, string content, and bodies with no messages field.

Type of change

  • Bug fix
  • Feature
  • Refactor
  • Documentation
  • Chore/CI

Affected areas

  • Core (Go)
  • Transports (HTTP)
  • Providers/Integrations
  • Plugins
  • UI (React)
  • Docs

How to test

go test ./core/providers/anthropic/... -run TestStripEmptyThinkingBlocks -v
go test ./...

The new TestStripEmptyThinkingBlocks table-driven test covers all relevant cases. Pay particular attention to the "strips block with non-empty thinking but empty signature (OpenAI/Gemini cross-provider replay)" case, which validates the core fix.

Screenshots/Recordings

N/A

Breaking changes

  • No

Related issues

Security considerations

None.

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

Summary by CodeRabbit

  • Bug Fixes

    • More consistent removal of empty "thinking" blocks: blocks are now stripped when either the thinking content or its signature is empty, and this cleaning runs for all request payload paths.
  • Tests

    • Added comprehensive tests covering multiple request shapes to validate thinking-block filtering behavior.

@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

An error occurred during the review process. Please try again later.

📝 Walkthrough

Walkthrough

StripEmptyThinkingBlocks now removes Anthropic "thinking" blocks when either thinking or signature is empty; the call is moved later in BuildAnthropicResponsesRequestBody so it runs for both raw and typed request bodies. Tests added to validate multiple input shapes and behaviors.

Changes

Anthropic Empty Thinking Block Handling

Layer / File(s) Summary
Enhanced empty thinking block stripping logic
core/providers/anthropic/utils.go
StripEmptyThinkingBlocks now strips thinking blocks when either thinking is empty or signature is empty; docstring updated and deletion flow unchanged.
Request builder pipeline refactoring
core/providers/anthropic/requestbuilder.go
The early raw-body StripEmptyThinkingBlocks call was removed and a single StripEmptyThinkingBlocks(jsonBody) invocation was added after raw/typed branches converge, with marshal/error handling at that point.
Comprehensive test suite for thinking block stripping
core/providers/anthropic/utils_test.go
TestStripEmptyThinkingBlocks (table-driven) verifies removal when thinking or signature is empty, preservation when both present, non-array content skipping, and unaffected non-thinking block types.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • danpiths
  • akshaydeo

Poem

I munch on bytes and hop through code,
I trim blank thoughts where signatures erode,
I moved the snip to where branches meet,
Tests now prove the message is neat,
Hooray — clean payloads, light and stowed 🐇✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: extending the stripping logic to remove thinking blocks with empty signatures.
Description check ✅ Passed The description covers all required sections with detailed explanations of the problem, solution, type of change, affected areas, testing instructions, and checklist items.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 06-05-fix_strip_thinking_block_if_signature_is_empty

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


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

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.


tejas ghatte seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

TejasGhatte commented Jun 5, 2026

Copy link
Copy Markdown
Collaborator Author

@TejasGhatte
TejasGhatte marked this pull request as ready for review June 5, 2026 09:45
@greptile-apps

greptile-apps Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Confidence Score: 5/5

Safe to merge — targeted, well-tested bug fix with no regressions on existing paths.

The logic change is minimal and correct: extending the strip condition to also check for empty signature precisely matches the failure mode. Moving the call outside the if/else ensures both raw-body and schema-converted paths are covered. The test suite covers all meaningful input states.

No files require special attention.

Important Files Changed

Filename Overview
core/providers/anthropic/utils.go Extends StripEmptyThinkingBlocks to also strip thinking blocks with an empty "signature" field, covering the cross-provider replay case.
core/providers/anthropic/requestbuilder.go Moves StripEmptyThinkingBlocks call to after both raw-body and converted-body branches so it applies uniformly to the final JSON regardless of input path.
core/providers/anthropic/utils_test.go Adds comprehensive table-driven tests for StripEmptyThinkingBlocks covering all relevant cases including the new empty-signature path.

Reviews (2): Last reviewed commit: "fix: strip thinking block if signature i..." | Re-trigger Greptile

@TejasGhatte
TejasGhatte changed the base branch from dev to graphite-base/4079 June 5, 2026 12:02
@TejasGhatte
TejasGhatte force-pushed the 06-05-fix_strip_thinking_block_if_signature_is_empty branch from 90bd65b to aa07dee Compare June 5, 2026 12:02
@TejasGhatte
TejasGhatte changed the base branch from graphite-base/4079 to 06-05-fix_remove_usage_from_responses.created_event_in_openai_stream June 5, 2026 12:02

akshaydeo commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Merge activity

  • Jun 5, 1:39 PM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Jun 5, 1:41 PM UTC: @akshaydeo merged this pull request with Graphite.

@akshaydeo
akshaydeo changed the base branch from 06-05-fix_remove_usage_from_responses.created_event_in_openai_stream to graphite-base/4079 June 5, 2026 13:40
@akshaydeo
akshaydeo changed the base branch from graphite-base/4079 to dev June 5, 2026 13:40
@akshaydeo
akshaydeo merged commit c5bc3c4 into dev Jun 5, 2026
11 of 12 checks passed
@akshaydeo
akshaydeo deleted the 06-05-fix_strip_thinking_block_if_signature_is_empty branch June 5, 2026 13:41
@akshaydeo akshaydeo mentioned this pull request Jun 5, 2026
18 tasks
akshaydeo added a commit that referenced this pull request Jun 6, 2026
## Summary

This PR bumps the Go toolchain version from `1.26.3` to `1.26.4` across all modules and CI workflows, and cuts a new release (`core` v1.5.17, `framework` v1.3.17, `transports` v1.5.9, `plugins/compat` v0.1.16, `plugins/governance` v1.5.17, and associated plugin versions) incorporating a large batch of features and fixes accumulated since the previous release.

## Changes

- **Go 1.26.4** — Updated `go-version` in all GitHub Actions workflows (`e2e-tests`, `helm-release`, `pr-tests`, `release-cli`, `release-pipeline`, `snyk`) and all `go.mod` files (core, framework, transports, cli, all plugins, examples, and test modules).
- **Core (v1.5.17)** — OpenAI compaction support, multi-customer logs and usage tracking, multiple team/business unit support, `request_headers` wildcard pattern capture for OTel and Maxim plugins, xAI `x_search` tool, fetch URL validation with SSRF hardening, `file://` pricing URL scheme, virtual key provider fan-out filtering, and a broad set of fixes including Anthropic prompt cache key, empty thinking block stripping, OpenAI stream usage event cleanup, Gemini numeric schema constraints, stale connection retries, Azure Claude diagnostic strip, and passthrough budget handling.
- **Framework (v1.3.17)** — Scope-aware budgets and limits wired from model configs, provider-level governance, multiple customer budget support with `calendar_aligned` windows, paginated virtual key fetch, `config.json` source-of-truth flow, FTS index cap reduction, sync worker drift fix, cascade deletes for model configs, and high-scale virtual key flow improvements.
- **Transports (v1.5.9)** — Full changelog covering all of the above plus UI improvements (log navigation, customer detail sheet, `BudgetDisplay` component, inline loading shell, materialized view alias), SCIM provisioning fields, Helm/config schema additions (`roles`, `per_user_oauth`), client IP resolution from forwarded headers, and dependency upgrades (`recharts` to 3.8.1, `golang.org/x` CVE remediation).
- **Plugins** — `governance` v1.5.17 adds team budget/rate-limit exporters, ghost node reconciliation fix, and VK double usage counting fix; `logging` v1.5.17 adds wildcard header capture and file attachment rendering; `otel` v1.2.17 adds `disable_content_logging` and multiple collectors support; `maxim` v1.6.17 adds `request_headers` wildcard capture; `compat` v0.1.16 fixes `max_tokens` preservation during param filtering.

## Type of change

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [x] Chore/CI

## Affected areas

- [x] Core (Go)
- [x] Transports (HTTP)
- [x] Providers/Integrations
- [x] Plugins
- [x] UI (React)
- [ ] Docs

## How to test

```sh
# Verify Go version
go version  # should report go1.26.4

# Run core tests
cd core && go test ./...

# Run framework tests
cd framework && go test ./...

# Run transports tests
cd transports && go test ./...

# Run plugin tests
cd plugins/governance && go test ./...
cd plugins/logging && go test ./...
cd plugins/otel && go test ./...

# UI
cd ui
pnpm i
pnpm build
pnpm test
```

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

#4053, #4066, #4041, #4012, #3976, #3947, #3991, #4045, #3957, #3938, #3937, #3939, #3981, #3998, #3997, #4092, #4091, #4079, #4080, #4086, #3929, #3994, #4028, #3970, #3919, #3861, #3664, #3999, #4088, #4070, #4051, #4043, #4057, #4023, #3941, #3955, #4024, #3956, #3967, #3925, #3992, #3900

## Security considerations

- Fetch URL validation hardened against SSRF by tightening IP checks for private networks and link-local addresses (#4092, #3947, #3991).
- Transitive `golang.org/x` dependencies (crypto, net, sys, text) bumped to address Docker Scout CVEs (#3900).

## 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

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

* **New Features**
  * OpenAI compaction, multi-customer/team logstore support, request-header wildcard capture, enhanced governance (provider-level & scope-aware limits), disable-content-logging option, support for multiple OpenTelemetry collectors, SSRF hardening and URL validation.

* **Chores**
  * Bumped Go toolchain across modules and updated component/plugin version releases.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
@akshaydeo akshaydeo mentioned this pull request Jun 7, 2026
akshaydeo pushed a commit that referenced this pull request Jun 7, 2026
## Summary

`StripEmptyThinkingBlocks` previously only stripped thinking blocks where the `thinking` field was empty. This missed a critical cross-provider case: when a thinking block originates from a non-Anthropic upstream (e.g. OpenAI, Gemini), it will have a non-empty `thinking` field but an empty `signature` field. Anthropic always emits a signature; OpenAI never does. Replaying such blocks to Anthropic causes a 400 rejection. This PR extends the stripping logic to also remove thinking blocks with an empty `signature`, and moves the `StripEmptyThinkingBlocks` call to after all other request body transformations so it applies to the fully-built body.

## Changes

- `StripEmptyThinkingBlocks` now strips thinking blocks where either `thinking` or `signature` is empty, covering both the original Anthropic-empty-thinking case and the cross-provider replay case where a signature is absent.
- The `StripEmptyThinkingBlocks` call was moved from inside the initial marshal block to after all subsequent transformations (tool version remapping, etc.), ensuring it operates on the final request body.
- Tests were added covering: empty thinking + empty signature, non-empty thinking but empty signature (OpenAI/Gemini replay), valid Anthropic blocks, mixed valid/invalid blocks, `redacted_thinking` blocks (unaffected), multi-message bodies, string content, and bodies with no `messages` field.

## Type of change

- [x] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [x] Core (Go)
- [ ] Transports (HTTP)
- [x] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

```sh
go test ./core/providers/anthropic/... -run TestStripEmptyThinkingBlocks -v
go test ./...
```

The new `TestStripEmptyThinkingBlocks` table-driven test covers all relevant cases. Pay particular attention to the `"strips block with non-empty thinking but empty signature (OpenAI/Gemini cross-provider replay)"` case, which validates the core fix.

## Screenshots/Recordings

N/A

## Breaking changes

- [x] No

## Related issues

## Security considerations

None.

## Checklist

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

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

* **Bug Fixes**
  * More consistent removal of empty "thinking" blocks: blocks are now stripped when either the thinking content or its signature is empty, and this cleaning runs for all request payload paths.

* **Tests**
  * Added comprehensive tests covering multiple request shapes to validate thinking-block filtering behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
akshaydeo added a commit that referenced this pull request Jun 7, 2026
## ✨ Features

- **OpenAI Compaction** — Added OpenAI conversation compaction support
across core, framework, logging, and the API surface (#4053)
- **Multi-Customer & Org Hierarchy** — Logs and usage tracking now
support multiple customers, teams, and business units, including
business unit CRUD, team assignment, and governance endpoints in the
OpenAPI spec (#4066, #4041, #4082)
- **Provider-Level Governance** — Budgets & limits are now scope-aware
and can be applied at the virtual-key top level and per provider, wired
from the model configs table, with UI filters for scope and providers
(#3938, #3937, #3939, #3981, #3962)
- **Customer Budgets** — Customers support multiple budgets and
`calendar_aligned` budget windows (#3998, #3997)
- **Virtual Key Attribution & Controls** — Added a `created_by` user
attribution column and a `blacklisted_models` column for virtual key
provider configs (#3672, #3653)
- **Request Header Capture** — OTel and Maxim observability plugins
capture `request_headers` by pattern, with wildcard support (e.g.
`x-custom-*`); logging gained the same wildcard header capture (#4012,
#3958)
- **OTel Content Controls & Collectors** — New `disable_content_logging`
option drops message/tool content from exported spans, plus support for
multiple OTel collectors (#4064, #3894)
- **xAI x_search** — Added xAI `x_search` tool support (#3976)
- **URL Validation** — Added fetch URL validation with private-network
configuration and link-local blocking (#3947, #3991)
- **File Scheme Pricing URLs** — Pricing source URLs now accept the
`file://` scheme for air-gapped and self-hosted deployments (#4045)
- **Paginated Virtual Keys** — Virtual key fetching is paginated to
handle deployments with very large numbers of keys (#3957)
- **Client IP Resolution** — Resolve client IP from
`X-Forwarded-For`/`X-Real-IP` headers
- **SCIM Provisioning** — Added `attributeType`/`attributeValue` SCIM
provisioning fields
- **Helm/Config Schema** — Added `roles` RBAC governance config and
`per_user_oauth` MCP auth to the Helm chart and config schema (#4004,
#4009)
- **Log Navigation UI** — Added a "View logs" menu item to customer,
team, and virtual key tables, clickable links in log detail views, a
customer detail sheet, and a reusable `BudgetDisplay` component (#4073,
#4054, #4026, #4055)
- **Faster First Paint** — Added an inline loading shell to `#root`
before React mounts (#4063)
- **Materialized View Alias** — Added an `alias` column to the
materialized view with filter support (#4078)

## 🐞 Fixed

- **Fetch URL IP Checks** — Hardened fetch URL IP checks against SSRF
(#4092)
- **Mantle Model Matching** — Broadened Mantle model matching to all
`gpt` variants (#4091)
- **Empty Thinking Blocks** — Strip thinking blocks when the signature
is empty (#4079)
- **OpenAI Stream Usage** — Removed usage from the `responses.created`
event in the OpenAI stream (#4080)
- **Prompt Cache Key** — Set the prompt cache key from the Anthropic
integration (#4086)
- **Upstream Failure Status** — Map upstream connection failures to 502
instead of 400 (#3929) (thanks
[@chris-colinsky](https://github.com/chris-colinsky)!)
- **Gemini Schema Constraints** — Accept numeric schema integer
constraints for Gemini (#3994) (thanks
[@yanhao98](https://github.com/yanhao98)!)
- **Files Provider Param** — Accept the `?provider=` query param on `GET
/v1/files` (#3971) (thanks [@alexef](https://github.com/alexef)!)
- **Optional Batch Model** — Made the `model` field optional on `POST
/v1/batches` (#3973) (thanks [@alexef](https://github.com/alexef)!)
- **Helm Azure Config** — Added missing `azure_key_config` fields to the
Helm schema (#3996) (thanks
[@axelray-dev](https://github.com/axelray-dev)!)
- **Text Completion Chunk Model** — Added the missing `Model` field to
`TextCompletionChunkResponse` (#3970) (thanks
[@kuishou68](https://github.com/kuishou68)!)
- **MCP Inline stdio Env** — MCP stdio server configs accept inline
environment variable assignments (#3861) (thanks
[@Shushmitaaaa](https://github.com/Shushmitaaaa)!)
- **Orphaned Tool Results** — Orphaned tool results in the OpenAI to
Anthropic conversion flow are no longer rejected by the Anthropic API
(#3919)
- **Node Usage Reconciliation** — Added a monotonic `inc_number` log
cursor so node usage reconciliation does not skip late async log writes
(#3664)
- **Bedrock Output Assessments** — Corrected the type of
`outputAssessments` in Bedrock responses (#4028)
- **Model Pool Pricing Reloads** — Preserve non-pricing model pool
entries across pricing reloads (#3999)
- **Ghost Node Reconciliation** — Replicate the VK hierarchy flow for
ghost node reconciliation (#4088)
- **VK Double Usage Counting** — Fixed double usage counting when
creating a virtual key (#4070)
- **Model Config Lifecycle** — Cascade deletes for model configs and
removal of stale in-memory model configs (#4051, #4043)
- **FTS Index Cap** — Reduced the FTS index `left()` cap from 800k to
250k chars to stay within the tsvector limit (#4057)
- **Sync Worker Drift** — Reduced the sync worker ticker period to 5m to
prevent threshold drift (#4023)
- **Passthrough** — Fixed passthrough budgets, gated passthrough models
per VK, model extraction for Azure passthrough, and restricted
fallbacks/provider selection to the VK boundary (#3941, #3988, #3983,
#3924)
- **Provider Response Headers** — Strip provider response headers and
add a content-type filter (#3955, #4024)
- **Stream Handling** — Drain non-SSE stream readers and retry stale
connections (#3956, #3967)
- **Azure Claude** — Strip Azure diagnostic property for Claude models
(#3925)
- **Compat max_tokens** — Preserve chat `max_tokens` during param
filtering (#3992)
- **Raw Request Flag** — Removed the raw request flag from providers
that don't support it (#4058)
- **UI Fixes** — Standardized page container layout, virtual key model
configs UI, and dashboard chart tooltips (#4046, #4052, #4044)

## 🔧 Maintenance

- **Dependency Upgrades** — Bumped transitive `golang.org/x`
dependencies (crypto, net, sys, text) for Docker Scout CVE remediation
and `recharts` to 3.8.1; cascaded version bumps across all modules
(#3900, #4003)
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.

3 participants