Skip to content

fix(agent): defer warning messages after parallel tool results#644

Merged
viettranx merged 1 commit intomainfrom
hotfix/parallel-tool-result-ordering
Apr 2, 2026
Merged

fix(agent): defer warning messages after parallel tool results#644
viettranx merged 1 commit intomainfrom
hotfix/parallel-tool-result-ordering

Conversation

@viettranx
Copy link
Copy Markdown
Contributor

Summary

  • Parallel tool calls + loop detection warning → user message inserted between tool results → breaks Anthropic API via LiteLLM proxy (HTTP 400)
  • Fix: accumulate warnings during parallel result processing, append after all tool results

Root Cause

processToolResult generates warning messages (role="user") for loop/same-result detection. In parallel path, these were appended inline between role="tool" messages. LiteLLM groups consecutive tool messages into one tool_result block — an intervening user message splits the group, orphaning subsequent tool results.

Changes

  • internal/agent/loop.go: Replace inline warning append with deferred accumulation + post-loop append

Safety

  • Single-tool path unchanged
  • pendingMsgs (session persistence) unaffected — warnings were never persisted
  • Warning position relative to tool results doesn't affect LLM behavior (informational hints)
  • All providers compatible (Anthropic, OpenAI-compat, DashScope, Claude CLI, ACP, Codex)

Closes #642

Test plan

  • go build ./... (PG) pass
  • go build -tags sqliteonly ./... (SQLite) pass
  • go vet ./internal/agent/ pass
  • Verified with production traces (before/after)

When parallel tool calls trigger loop detection warnings, the warning
messages (role="user") were inserted between tool result messages
(role="tool"). This breaks the Anthropic API when routed through
OpenAI-compatible proxies (e.g. LiteLLM): the proxy groups consecutive
tool messages into a single user message with tool_result blocks, but
an intervening user warning splits the group, causing orphaned
tool_results and HTTP 400 "tool_use ids without tool_result blocks".

Fix: accumulate warning messages during parallel result processing and
append them after all tool results, preserving the consecutive grouping.

Closes #642
@viettranx viettranx merged commit 9be6c24 into main Apr 2, 2026
2 checks passed
viettranx added a commit that referenced this pull request Apr 2, 2026
* fix(secure-cli): resolve ambiguous column in LookupByBinary JOIN query (#641)

LookupByBinary uses LEFT JOIN with secure_cli_user_credentials but
SELECT columns lacked table alias prefix, causing PostgreSQL error:
"column reference 'id' is ambiguous (SQLSTATE 42702)"

This silently broke ALL credentialed CLI exec — commands fell through
to regular shell exec without injected env vars.

Fix: use b.-prefixed column names for JOIN queries.
Also add diagnostic logging to lookupCredentialedBinary for future debugging.

* fix(agent): defer warning messages after parallel tool results (#644)

When parallel tool calls trigger loop detection warnings, the warning
messages (role="user") were inserted between tool result messages
(role="tool"). This breaks the Anthropic API when routed through
OpenAI-compatible proxies (e.g. LiteLLM): the proxy groups consecutive
tool messages into a single user message with tool_result blocks, but
an intervening user warning splits the group, causing orphaned
tool_results and HTTP 400 "tool_use ids without tool_result blocks".

Fix: accumulate warning messages during parallel result processing and
append them after all tool results, preserving the consecutive grouping.

Closes #642

* fix(web): allow editing Brave Search API key when masked

---------

Co-authored-by: Viet Tran <viettranx@gmail.com>
@vanducng vanducng mentioned this pull request Apr 3, 2026
17 tasks
viettranx added a commit that referenced this pull request Apr 5, 2026
…ef handling, managed-by banner (#671)

* fix(secure-cli): resolve ambiguous column in LookupByBinary JOIN query (#641)

LookupByBinary uses LEFT JOIN with secure_cli_user_credentials but
SELECT columns lacked table alias prefix, causing PostgreSQL error:
"column reference 'id' is ambiguous (SQLSTATE 42702)"

This silently broke ALL credentialed CLI exec — commands fell through
to regular shell exec without injected env vars.

Fix: use b.-prefixed column names for JOIN queries.
Also add diagnostic logging to lookupCredentialedBinary for future debugging.

* fix(agent): defer warning messages after parallel tool results (#644)

When parallel tool calls trigger loop detection warnings, the warning
messages (role="user") were inserted between tool result messages
(role="tool"). This breaks the Anthropic API when routed through
OpenAI-compatible proxies (e.g. LiteLLM): the proxy groups consecutive
tool messages into a single user message with tool_result blocks, but
an intervening user warning splits the group, causing orphaned
tool_results and HTTP 400 "tool_use ids without tool_result blocks".

Fix: accumulate warning messages during parallel result processing and
append them after all tool results, preserving the consecutive grouping.

Closes #642

* fix(docker): resolve @rollup/rollup-linux-arm64-musl missing on Alpine (#647)

Added ui/web/.npmrc with supportedArchitectures for musl+glibc/arm64+x64.
Updated Dockerfile to use --no-frozen-lockfile so pnpm fetches native rollup
binding compatible with Alpine's musl libc. Lockfile still pinned by copy order.

* docs(README): add history stars (#462)

* fix(pool): skip stale pool member references during validation

Unknown pool member references (deleted or disabled providers) now
continue instead of returning an error. Prevents stale data from
blocking provider saves.

Closes #670

* fix(ui): redesign pool member selector and add managed-by banner

Pool member selector:
- Replace invisible outline button with custom element using dashed
  primary border, + icon badge, and "Click to add" hint text
- Visible in both light and dark themes; hover transitions to solid
  border with shadow; active press scales down for tactile feedback

Managed-by banner:
- Show "Pool Defaults" section on pool members with info banner
  explaining which provider owns the pool, plus a Link navigation
- Previously this section was completely hidden with no explanation

i18n: add poolManagedByDescription and clickToAdd keys (en/vi/zh)

* docs: add before/after UI evidence for PR #671

Annotated screenshots with red callout borders marking review areas.
Self-contained HTML comparison report with dark/light theme toggle.

* feat(ui): add pool discovery badges and setup wizard

Replace verbose info banner with per-card "Pool available" badge on
unpooled ChatGPT OAuth providers. Clicking the badge opens a new
pool setup wizard dialog where users select owner, members, and
strategy in one step.

* docs: update UI evidence with pool discovery before/after

* fix(ui): hide pool members from provider selector in agent forms

Pool member providers are managed via the pool owner's routing config.
Showing them as standalone options in the agent Provider dropdown is
confusing — users may select a member directly instead of the owner,
bypassing pool routing entirely.

Filter out providers that exist in ownerByMember from the enabled
providers list in ProviderModelSelect.

* fix(ui): hide pool members from provider selector and add Pool badge

Pool member providers are filtered out of the agent Provider dropdown
in both the Create Agent dialog and the shared ProviderModelSelect
component. Pool owners display a "Pool" badge so users know the
provider routes to multiple accounts automatically.

* docs: add provider selector before/after evidence

* fix: revert stale merge in secure_cli.go and fix hardcoded i18n strings

- Revert secureCLISelectColsAliased: b.agent_id → b.is_global
  (agent_id was dropped in migration 36, stale merge conflict artifact)
- Replace hardcoded "Pool" badge text with t("providers:list.poolBadge")
  in provider-model-select and agent-identity-and-model-fields
- Replace hardcoded "Disabled" with t("common:disabled") in pool wizard
- Add list.poolBadge key to en/vi/zh locale files

---------

Co-authored-by: Viet Tran <viettranx@gmail.com>
Co-authored-by: Plateau Nguyen <nguyennlt.ncc@gmail.com>
Co-authored-by: DNT <ducconit@gmail.com>
lecong pushed a commit to lecong/goclaw that referenced this pull request Apr 7, 2026
* fix(secure-cli): resolve ambiguous column in LookupByBinary JOIN query (nextlevelbuilder#641)

LookupByBinary uses LEFT JOIN with secure_cli_user_credentials but
SELECT columns lacked table alias prefix, causing PostgreSQL error:
"column reference 'id' is ambiguous (SQLSTATE 42702)"

This silently broke ALL credentialed CLI exec — commands fell through
to regular shell exec without injected env vars.

Fix: use b.-prefixed column names for JOIN queries.
Also add diagnostic logging to lookupCredentialedBinary for future debugging.

* fix(agent): defer warning messages after parallel tool results (nextlevelbuilder#644)

When parallel tool calls trigger loop detection warnings, the warning
messages (role="user") were inserted between tool result messages
(role="tool"). This breaks the Anthropic API when routed through
OpenAI-compatible proxies (e.g. LiteLLM): the proxy groups consecutive
tool messages into a single user message with tool_result blocks, but
an intervening user warning splits the group, causing orphaned
tool_results and HTTP 400 "tool_use ids without tool_result blocks".

Fix: accumulate warning messages during parallel result processing and
append them after all tool results, preserving the consecutive grouping.

Closes nextlevelbuilder#642

* fix(web): allow editing Brave Search API key when masked

---------

Co-authored-by: Viet Tran <viettranx@gmail.com>
lecong pushed a commit to lecong/goclaw that referenced this pull request Apr 7, 2026
…ef handling, managed-by banner (nextlevelbuilder#671)

* fix(secure-cli): resolve ambiguous column in LookupByBinary JOIN query (nextlevelbuilder#641)

LookupByBinary uses LEFT JOIN with secure_cli_user_credentials but
SELECT columns lacked table alias prefix, causing PostgreSQL error:
"column reference 'id' is ambiguous (SQLSTATE 42702)"

This silently broke ALL credentialed CLI exec — commands fell through
to regular shell exec without injected env vars.

Fix: use b.-prefixed column names for JOIN queries.
Also add diagnostic logging to lookupCredentialedBinary for future debugging.

* fix(agent): defer warning messages after parallel tool results (nextlevelbuilder#644)

When parallel tool calls trigger loop detection warnings, the warning
messages (role="user") were inserted between tool result messages
(role="tool"). This breaks the Anthropic API when routed through
OpenAI-compatible proxies (e.g. LiteLLM): the proxy groups consecutive
tool messages into a single user message with tool_result blocks, but
an intervening user warning splits the group, causing orphaned
tool_results and HTTP 400 "tool_use ids without tool_result blocks".

Fix: accumulate warning messages during parallel result processing and
append them after all tool results, preserving the consecutive grouping.

Closes nextlevelbuilder#642

* fix(docker): resolve @rollup/rollup-linux-arm64-musl missing on Alpine (nextlevelbuilder#647)

Added ui/web/.npmrc with supportedArchitectures for musl+glibc/arm64+x64.
Updated Dockerfile to use --no-frozen-lockfile so pnpm fetches native rollup
binding compatible with Alpine's musl libc. Lockfile still pinned by copy order.

* docs(README): add history stars (nextlevelbuilder#462)

* fix(pool): skip stale pool member references during validation

Unknown pool member references (deleted or disabled providers) now
continue instead of returning an error. Prevents stale data from
blocking provider saves.

Closes nextlevelbuilder#670

* fix(ui): redesign pool member selector and add managed-by banner

Pool member selector:
- Replace invisible outline button with custom element using dashed
  primary border, + icon badge, and "Click to add" hint text
- Visible in both light and dark themes; hover transitions to solid
  border with shadow; active press scales down for tactile feedback

Managed-by banner:
- Show "Pool Defaults" section on pool members with info banner
  explaining which provider owns the pool, plus a Link navigation
- Previously this section was completely hidden with no explanation

i18n: add poolManagedByDescription and clickToAdd keys (en/vi/zh)

* docs: add before/after UI evidence for PR nextlevelbuilder#671

Annotated screenshots with red callout borders marking review areas.
Self-contained HTML comparison report with dark/light theme toggle.

* feat(ui): add pool discovery badges and setup wizard

Replace verbose info banner with per-card "Pool available" badge on
unpooled ChatGPT OAuth providers. Clicking the badge opens a new
pool setup wizard dialog where users select owner, members, and
strategy in one step.

* docs: update UI evidence with pool discovery before/after

* fix(ui): hide pool members from provider selector in agent forms

Pool member providers are managed via the pool owner's routing config.
Showing them as standalone options in the agent Provider dropdown is
confusing — users may select a member directly instead of the owner,
bypassing pool routing entirely.

Filter out providers that exist in ownerByMember from the enabled
providers list in ProviderModelSelect.

* fix(ui): hide pool members from provider selector and add Pool badge

Pool member providers are filtered out of the agent Provider dropdown
in both the Create Agent dialog and the shared ProviderModelSelect
component. Pool owners display a "Pool" badge so users know the
provider routes to multiple accounts automatically.

* docs: add provider selector before/after evidence

* fix: revert stale merge in secure_cli.go and fix hardcoded i18n strings

- Revert secureCLISelectColsAliased: b.agent_id → b.is_global
  (agent_id was dropped in migration 36, stale merge conflict artifact)
- Replace hardcoded "Pool" badge text with t("providers:list.poolBadge")
  in provider-model-select and agent-identity-and-model-fields
- Replace hardcoded "Disabled" with t("common:disabled") in pool wizard
- Add list.poolBadge key to en/vi/zh locale files

---------

Co-authored-by: Viet Tran <viettranx@gmail.com>
Co-authored-by: Plateau Nguyen <nguyennlt.ncc@gmail.com>
Co-authored-by: DNT <ducconit@gmail.com>
@nguyennguyenit nguyennguyenit deleted the hotfix/parallel-tool-result-ordering branch April 16, 2026 07:33
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.

1 participant