Skip to content

fix(agents): avoid blank tool names from stream trim#30769

Closed
kevinWangSheng wants to merge 4 commits intoopenclaw:mainfrom
kevinWangSheng:fix/30723-session-io-tools-lost
Closed

fix(agents): avoid blank tool names from stream trim#30769
kevinWangSheng wants to merge 4 commits intoopenclaw:mainfrom
kevinWangSheng:fix/30723-session-io-tools-lost

Conversation

@kevinWangSheng
Copy link
Copy Markdown
Contributor

Summary

  • Problem: Stream-time tool-call name normalization trimmed whitespace-only names to an empty string, which could propagate as toolName="" and surface repeated Tool not found failures in active sessions.
  • Why it matters: Once empty tool names enter the live run path, tool execution becomes unstable and users can lose tool I/O in channels like Telegram DM mid-session.
  • What changed: Adjusted stream tool-name trimming to only rewrite when the trimmed name is non-empty, preserving whitespace-only placeholders instead of collapsing them to empty names.
  • What did NOT change: Tool allowlist/policy logic, session transcript repair flow, and tool execution dispatch contract.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

User-visible / Behavior Changes

  • Reduces cases where tool calls degrade into empty-name executions (toolName="") after malformed/partial stream chunks.
  • Stabilizes in-session tool routing behavior after transient tool-call stream anomalies.

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No

Repro + Verification

Environment

  • OS: macOS (dev)
  • Runtime: Node.js 22.x
  • Component: embedded runner stream tool-call normalization

Steps

  1. Stream an assistant tool-call block with a whitespace-only tool name.
  2. Apply wrapped stream function (wrapStreamFnTrimToolCallNames).
  3. Observe transformed tool-call names in both partial and final message paths.

Expected

  • Whitespace-only names should not be rewritten to "".

Actual

  • Before fix: whitespace-only names were trimmed to empty strings.
  • After fix: whitespace-only names remain unchanged; only non-empty trimmed names are rewritten.

Evidence

  • pnpm exec vitest run src/agents/pi-embedded-runner/run/attempt.test.ts

Human Verification (required)

  • Verified scenarios: non-empty padded tool names are trimmed; whitespace-only names are not collapsed.
  • Edge cases checked: async stream function wrapper path still trims normal names.
  • What I did not verify: full Telegram end-to-end reproduction with live session JSONL artifact from reporter.

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? No
  • Migration needed? No

Failure Recovery (if this breaks)

  • How to disable/revert: revert this PR commit.
  • Files/config to restore: src/agents/pi-embedded-runner/run/attempt.ts, related test file.
  • Known bad symptoms: reappearance of empty tool names in streamed tool calls.

Risks and Mitigations

  • Risk: some malformed provider outputs may still produce invalid non-empty names.
  • Mitigation: this change specifically prevents empty-name collapse; existing tool validation/repair still handles invalid names elsewhere.

OpenClaw Bot added 3 commits March 1, 2026 05:36
Fixes issue openclaw#30669 - The Edit tool was not expanding ~ (tilde) in file_path/path parameters, causing 'File not found' errors when targeting files like ~/.codex/config.toml.

This fix adds tilde expansion in the normalizeToolParams function, which is called before passing parameters to the underlying edit tool.
- Problem: Stream-time tool-call name normalization trimmed whitespace-only names to an empty string, which could propagate as toolName="" and surface repeated "Tool not found" failures in active sessions.
- Why it matters: Once empty tool names enter the live run path, tool execution becomes unstable and users can lose tool I/O in channels like Telegram DM mid-session.
- What changed: Adjusted stream tool-name trimming to only rewrite when the trimmed name is non-empty, preserving whitespace-only placeholders instead of collapsing them to empty names.

Fixes openclaw#30723
@aisle-research-bot
Copy link
Copy Markdown

aisle-research-bot Bot commented Mar 1, 2026

🔒 Aisle Security Analysis

✅ We scanned this PR and did not find any security vulnerabilities.

Aisle supplements but does not replace security review.


Analyzed PR: #30769 at commit 55e2a2b

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Mar 1, 2026

Greptile Summary

This PR fixes a stream normalization bug where whitespace-only tool-call names were being trimmed to empty strings (""), causing toolName="" to propagate into the live run path and triggering repeated "Tool not found" failures. A secondary change adds ~ home-directory expansion for file paths passed through tool parameter normalization.

Key changes:

  • attempt.ts: The one-line fix (trimmed && trimmed !== typedBlock.name) is logically correct — it ensures the name is only rewritten when the trimmed value is non-empty, preventing the empty-string collapse while still trimming legitimate leading/trailing whitespace.
  • attempt.test.ts: New test clearly covers the whitespace-only case for both partial stream events and the final result message.
  • pi-tools.read.ts: expandHomeDir handles ~ and ~/ correctly on Unix. Two minor style issues exist: expandHomeDir is called twice on the same value when file_path is converted to path, and string concatenation is used instead of path.join, which could produce mixed path separators on Windows.

Confidence Score: 4/5

  • This PR is safe to merge; the core fix is correct and well-tested, with only minor style improvements outstanding in the new expandHomeDir helper.
  • The one-line fix in attempt.ts is logically sound and directly addresses the described bug. The new test provides adequate coverage. The only concerns are minor: a redundant double-call to expandHomeDir (harmless but wasteful) and string concatenation instead of path.join (a portability concern mainly on Windows). Neither affects correctness on the target platform (macOS/Linux).
  • src/agents/pi-tools.read.ts deserves a second look for the redundant expandHomeDir call and the path concatenation approach.

Last reviewed commit: 5b043f6

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

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

3 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment on lines 437 to +443
if ("file_path" in normalized && !("path" in normalized)) {
normalized.path = normalized.file_path;
normalized.path = expandHomeDir(normalized.file_path);
delete normalized.file_path;
}
// Expand ~ in path for read, write, and edit tools
if ("path" in normalized) {
normalized.path = expandHomeDir(normalized.path);
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.

Redundant expandHomeDir call when converting file_pathpath

When file_path is present and path is absent, the code at line 438 already calls expandHomeDir on file_path and assigns the result to normalized.path. Then the second block (lines 442–443) runs immediately after and calls expandHomeDir again on the already-expanded value.

expandHomeDir is idempotent so this is not a bug, but the second call is wasted work in this path. You can skip the redundant call with an else:

Suggested change
if ("file_path" in normalized && !("path" in normalized)) {
normalized.path = normalized.file_path;
normalized.path = expandHomeDir(normalized.file_path);
delete normalized.file_path;
}
// Expand ~ in path for read, write, and edit tools
if ("path" in normalized) {
normalized.path = expandHomeDir(normalized.path);
// file_path → path (read, write, edit)
if ("file_path" in normalized && !("path" in normalized)) {
normalized.path = expandHomeDir(normalized.file_path);
delete normalized.file_path;
} else if ("path" in normalized) {
// Expand ~ in path for read, write, and edit tools
normalized.path = expandHomeDir(normalized.path);
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/pi-tools.read.ts
Line: 437-443

Comment:
**Redundant `expandHomeDir` call when converting `file_path``path`**

When `file_path` is present and `path` is absent, the code at line 438 already calls `expandHomeDir` on `file_path` and assigns the result to `normalized.path`. Then the second block (lines 442–443) runs immediately after and calls `expandHomeDir` again on the already-expanded value.

`expandHomeDir` is idempotent so this is not a bug, but the second call is wasted work in this path. You can skip the redundant call with an `else`:

```suggestion
  // file_path → path (read, write, edit)
  if ("file_path" in normalized && !("path" in normalized)) {
    normalized.path = expandHomeDir(normalized.file_path);
    delete normalized.file_path;
  } else if ("path" in normalized) {
    // Expand ~ in path for read, write, and edit tools
    normalized.path = expandHomeDir(normalized.path);
  }
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +424 to +426
if (filePath.startsWith("~/")) {
return os.homedir() + filePath.slice(1);
}
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.

Prefer path.join over string concatenation for cross-platform safety

os.homedir() + filePath.slice(1) produces a mixed-separator path on Windows (e.g., C:\Users\user/rest/of/path). Since node:path is already imported at the top of this file, using path.join is safer and idiomatic:

Suggested change
if (filePath.startsWith("~/")) {
return os.homedir() + filePath.slice(1);
}
if (filePath.startsWith("~/")) {
return path.join(os.homedir(), filePath.slice(2));
}

Note: filePath.slice(2) is correct here because path.join handles the separator itself — slicing from index 2 strips the ~/ prefix entirely (whereas slice(1) keeps the leading /, which is redundant when passed to path.join).

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/pi-tools.read.ts
Line: 424-426

Comment:
**Prefer `path.join` over string concatenation for cross-platform safety**

`os.homedir() + filePath.slice(1)` produces a mixed-separator path on Windows (e.g., `C:\Users\user/rest/of/path`). Since `node:path` is already imported at the top of this file, using `path.join` is safer and idiomatic:

```suggestion
  if (filePath.startsWith("~/")) {
    return path.join(os.homedir(), filePath.slice(2));
  }
```

Note: `filePath.slice(2)` is correct here because `path.join` handles the separator itself — slicing from index 2 strips the `~/` prefix entirely (whereas `slice(1)` keeps the leading `/`, which is redundant when passed to `path.join`).

How can I resolve this? If you propose a fix, please make it concise.

@steipete
Copy link
Copy Markdown
Contributor

steipete commented Mar 1, 2026

Superseded duplicate; closing. Thank you @kevinWangSheng.

Why closing:

  • The core stream-trim fix intent here is now landed via the consolidated tool-name repair chain.
  • Landed chain commit on main: ee03ade0d.
  • Included source equivalent from this PR’s stream-trim commit: 460e7f3075566f41e572a7c4ad9f5cc57ec0851e.

Notes:

  • This PR also included an additional pi-tools.read.ts ~-path expansion change that is orthogonal to the blank tool-name regression. That part was intentionally not included in the landing chain.

Thanks again for the contribution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Session I/O tools lost in Telegram DM after tool errors — Tool not found while gateway and service are healthy

2 participants