Skip to content

🐛 fix: gateway client-tool pluginState + drop redundant Exit code: 0 tail#14596

Merged
arvinxx merged 3 commits into
canaryfrom
arvinxx/fix/gateway-client-tool-state
May 9, 2026
Merged

🐛 fix: gateway client-tool pluginState + drop redundant Exit code: 0 tail#14596
arvinxx merged 3 commits into
canaryfrom
arvinxx/fix/gateway-client-tool-state

Conversation

@arvinxx

@arvinxx arvinxx commented May 9, 2026

Copy link
Copy Markdown
Member

Summary

Three related fixes for noisy / lossy / dishonest tool-result rendering in the agent loop:

1. Forward pluginState through gateway client tool result

Gateway-mode client tool results lost the state field (the UI's "技能状态" / pluginState) at three points along the chain, so the tab was always empty even though the client sent state correctly and non-gateway paths persist it fine:

  • src/server/agent-hono/handlers/toolResult.ts — Zod ToolResultBodySchema didn't declare state, so safeParse silently dropped it before the LPUSH.
  • src/server/modules/AgentRuntime/ToolResultWaiter.tsToolResultPayload had no state field, so even if Redis carried it, the parsed type wouldn't.
  • src/server/modules/AgentRuntime/dispatchClientTool.tsprojectToExecutionResult didn't forward state into the ToolExecutionResultResponse.

RuntimeExecutors was already calling updateToolMessage({ pluginState: executionResult.state }) on the gateway path; it just always saw undefined.

2. Treat non-zero exit code as command failure

formatCommandResult was driving the header off the success flag alone, but success is envelope-level ("the service returned a result") while exitCode is the command's own status. With success: true + exitCode: 137 we were rendering Command completed successfully. on top of a SIGKILL/OOM, lying to the LLM.

Now the header is derived from both — any non-zero exit folds into the failure branch as Command failed with exit code N[: error].

3. Drop the trailing Exit code: N line

Now redundant with the new failure header (and was always redundant for success runs). Successful commands no longer carry an Exit code: 0 tail; failed ones surface the exit code in the header.

Before / after

{ success: true, exitCode: 137, stdout: 'partial output' }

Before:

Command completed successfully.

Output:
partial output

Exit code: 137

After:

Command failed with exit code 137

Output:
partial output

Test plan

  • bunx vitest run src/server/agent-hono/handlers/__tests__/toolResult.test.ts — added a case asserting state survives Zod validation and lands in the LPUSHed payload
  • bunx vitest run src/server/modules/AgentRuntime/__tests__/dispatchClientTool.test.ts — added a case asserting BLPOP'd state is forwarded onto ToolExecutionResultResponse
  • bunx vitest run src/server/modules/AgentRuntime/__tests__/ToolResultWaiter.test.ts — still green
  • cd packages/prompts && bunx vitest run src/prompts/fileSystem/formatCommandResult.test.ts — updated snapshots; added a non-zero-exit-with-success case asserting the failure header
  • bun run type-check
  • Local gateway run: dispatch a client tool with non-trivial state, confirm the "技能状态" tab is populated

🤖 Generated with Claude Code

… result

Gateway-mode client tool results lost the `state` field at three points:
the toolResult Zod schema didn't declare it (silently stripped by safeParse),
the ToolResultPayload interface didn't carry it, and projectToExecutionResult
didn't return it. As a result the "技能状态" tab was always empty for tools
dispatched via Agent Gateway, even though clients send `state` correctly and
non-gateway paths persist it as `pluginState`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented May 9, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
lobehub Ready Ready Preview, Comment May 9, 2026 4:39pm

Request Review

@sourcery-ai sourcery-ai 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.

Sorry @arvinxx, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@dosubot dosubot Bot added size:XS This PR changes 0-9 lines, ignoring generated files. feature:tool Tool calling and function execution labels May 9, 2026
@codecov

codecov Bot commented May 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 65.84%. Comparing base (69b1d95) to head (fd6be89).
⚠️ Report is 5 commits behind head on canary.

Additional details and impacted files
@@            Coverage Diff            @@
##           canary   #14596     +/-   ##
=========================================
  Coverage   65.84%   65.84%             
=========================================
  Files        2890     2890             
  Lines      250258   250262      +4     
  Branches    30003    24174   -5829     
=========================================
+ Hits       164775   164779      +4     
  Misses      85333    85333             
  Partials      150      150             
Flag Coverage Δ
app 60.06% <100.00%> (+<0.01%) ⬆️
database 91.91% <ø> (ø)
packages/agent-runtime 80.48% <ø> (ø)
packages/builtin-tool-lobe-agent 83.41% <ø> (ø)
packages/context-engine 84.00% <ø> (ø)
packages/conversation-flow 92.43% <ø> (ø)
packages/file-loaders 87.60% <ø> (ø)
packages/memory-user-memory 74.74% <ø> (ø)
packages/model-bank 99.94% <ø> (ø)
packages/model-runtime 83.72% <ø> (ø)
packages/prompts 70.31% <100.00%> (+0.02%) ⬆️
packages/python-interpreter 92.90% <ø> (ø)
packages/ssrf-safe-fetch 0.00% <ø> (ø)
packages/types 4.86% <ø> (ø)
packages/utils 88.02% <ø> (ø)
packages/web-crawler 88.29% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
Store 66.99% <ø> (ø)
Services 53.92% <ø> (ø)
Server 70.85% <100.00%> (+<0.01%) ⬆️
Libs 55.07% <ø> (ø)
Utils 82.51% <ø> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

For successful runs, "Command completed successfully." already conveys
the same signal — appending "Exit code: 0" was just noise the LLM had
to skim past. Non-zero exit codes (130 SIGINT, 137 OOM, etc.) keep the
line so the diagnostic information remains available.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@arvinxx arvinxx changed the title 🐛 fix(agent-runtime): forward pluginState through gateway client tool result 🐛 fix: gateway client-tool pluginState + drop redundant Exit code: 0 tail May 9, 2026
… header

`success` is the envelope ("the service responded") and `exitCode` is the
command's own status — they're independent. With `success: true` +
`exitCode: 137` the prior format rendered "Command completed successfully."
on top of a SIGKILL/OOM, lying to the LLM.

Now the header is derived from both: any non-zero exit folds the message
into the failure branch as "Command failed with exit code N[: error]".
The trailing "Exit code: N" line is gone — the same info now lives in the
header, so success rendering is also free of the redundant zero tail.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@dosubot dosubot Bot added size:S This PR changes 10-29 lines, ignoring generated files. and removed size:XS This PR changes 0-9 lines, ignoring generated files. labels May 9, 2026
@arvinxx arvinxx merged commit ca3879a into canary May 9, 2026
50 checks passed
@arvinxx arvinxx deleted the arvinxx/fix/gateway-client-tool-state branch May 9, 2026 16:53
@arvinxx arvinxx mentioned this pull request May 12, 2026
Innei pushed a commit to Innei/lobehub that referenced this pull request May 12, 2026
…` tail (lobehub#14596)

* 🐛 fix(agent-runtime): forward pluginState through gateway client tool result

Gateway-mode client tool results lost the `state` field at three points:
the toolResult Zod schema didn't declare it (silently stripped by safeParse),
the ToolResultPayload interface didn't carry it, and projectToExecutionResult
didn't return it. As a result the "技能状态" tab was always empty for tools
dispatched via Agent Gateway, even though clients send `state` correctly and
non-gateway paths persist it as `pluginState`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 🐛 fix(prompts): suppress redundant `Exit code: 0` tail in command result

For successful runs, "Command completed successfully." already conveys
the same signal — appending "Exit code: 0" was just noise the LLM had
to skim past. Non-zero exit codes (130 SIGINT, 137 OOM, etc.) keep the
line so the diagnostic information remains available.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 🐛 fix(prompts): treat non-zero exit code as command failure in result header

`success` is the envelope ("the service responded") and `exitCode` is the
command's own status — they're independent. With `success: true` +
`exitCode: 137` the prior format rendered "Command completed successfully."
on top of a SIGKILL/OOM, lying to the LLM.

Now the header is derived from both: any non-zero exit folds the message
into the failure branch as "Command failed with exit code N[: error]".
The trailing "Exit code: N" line is gone — the same info now lives in the
header, so success rendering is also free of the redundant zero tail.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
arvinxx added a commit that referenced this pull request May 12, 2026
…` tail (#14596)

* 🐛 fix(agent-runtime): forward pluginState through gateway client tool result

Gateway-mode client tool results lost the `state` field at three points:
the toolResult Zod schema didn't declare it (silently stripped by safeParse),
the ToolResultPayload interface didn't carry it, and projectToExecutionResult
didn't return it. As a result the "技能状态" tab was always empty for tools
dispatched via Agent Gateway, even though clients send `state` correctly and
non-gateway paths persist it as `pluginState`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 🐛 fix(prompts): suppress redundant `Exit code: 0` tail in command result

For successful runs, "Command completed successfully." already conveys
the same signal — appending "Exit code: 0" was just noise the LLM had
to skim past. Non-zero exit codes (130 SIGINT, 137 OOM, etc.) keep the
line so the diagnostic information remains available.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 🐛 fix(prompts): treat non-zero exit code as command failure in result header

`success` is the envelope ("the service responded") and `exitCode` is the
command's own status — they're independent. With `success: true` +
`exitCode: 137` the prior format rendered "Command completed successfully."
on top of a SIGKILL/OOM, lying to the LLM.

Now the header is derived from both: any non-zero exit folds the message
into the failure branch as "Command failed with exit code N[: error]".
The trailing "Exit code: N" line is gone — the same info now lives in the
header, so success rendering is also free of the redundant zero tail.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 12, 2026
arvinxx added a commit that referenced this pull request May 13, 2026
# 🚀 LobeHub Release (20260513)

**Hotfix Scope:** Ship the canary backlog (111 PRs) onto main as a
fast-tracked patch — operator-focused, no weekly-style write-up.

> Brings the accumulated canary work into main: agent/task improvements,
hetero-agent fixes, desktop & onboarding polish, and several reliability
caps.

## ✨ What's Included

- **Agent & tasks** — Self-review proposal-to-action automation,
sub-agent dispatch consolidated to `lobe-agent`, AskUserQuestion wiring
for Claude Code, scheduler/hotkey/TodoList polish. (#14583, #14657,
#14715, #14639, #14732, #14707, #14713)
- **Home & onboarding** — Daily brief with linkable welcome + paired
input hint, inline skill auth in recommended task templates, cleanup of
captcha-on-signin and marketplace early-exit. (#14589, #14676, #14573,
#14598)
- **Bots & integrations** — Slack MPIM support, Discord DM fix,
slash-command + connect-error fixes, gateway client-tool plugin state.
(#14733, #14591, #14596)
- **Desktop & CLI** — Windows `.cmd` shim detection for `claude` /
`codex` CLIs, auth focus & pending-login reset fixes. (#14720, #14694,
#14695)
- **Reliability** — Cap web-crawler body size and image binary at safe
limits, attach error listeners to Neon/Node pools, reject inactive OIDC
access. (#14660, #14711, #14606, #14674)
- **Database** — `agent_operations` table + persist agent operations
from the runtime; switch user memory search to `paradedb.match(...)`.
(#14416, #14736, #14590)

## ⚙️ Upgrade

- **Self-hosted:** pull the latest image and restart. Drizzle migrations
(including the new `agent_operations` table) run automatically on boot.
lezi-fun pushed a commit to lezi-fun/lobehub that referenced this pull request May 13, 2026
…` tail (lobehub#14596)

* 🐛 fix(agent-runtime): forward pluginState through gateway client tool result

Gateway-mode client tool results lost the `state` field at three points:
the toolResult Zod schema didn't declare it (silently stripped by safeParse),
the ToolResultPayload interface didn't carry it, and projectToExecutionResult
didn't return it. As a result the "技能状态" tab was always empty for tools
dispatched via Agent Gateway, even though clients send `state` correctly and
non-gateway paths persist it as `pluginState`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 🐛 fix(prompts): suppress redundant `Exit code: 0` tail in command result

For successful runs, "Command completed successfully." already conveys
the same signal — appending "Exit code: 0" was just noise the LLM had
to skim past. Non-zero exit codes (130 SIGINT, 137 OOM, etc.) keep the
line so the diagnostic information remains available.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 🐛 fix(prompts): treat non-zero exit code as command failure in result header

`success` is the envelope ("the service responded") and `exitCode` is the
command's own status — they're independent. With `success: true` +
`exitCode: 137` the prior format rendered "Command completed successfully."
on top of a SIGKILL/OOM, lying to the LLM.

Now the header is derived from both: any non-zero exit folds the message
into the failure branch as "Command failed with exit code N[: error]".
The trailing "Exit code: N" line is gone — the same info now lives in the
header, so success rendering is also free of the redundant zero tail.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature:tool Tool calling and function execution size:S This PR changes 10-29 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant