Skip to content

fix: detect duplicate tool names in buildToolMap#91

Merged
vietanh85 merged 4 commits into
zendev-sh:mainfrom
bclermont:fix/duplicate-tool-names
Jun 8, 2026
Merged

fix: detect duplicate tool names in buildToolMap#91
vietanh85 merged 4 commits into
zendev-sh:mainfrom
bclermont:fix/duplicate-tool-names

Conversation

@bclermont

Copy link
Copy Markdown
Contributor

Problem

buildToolMap builds a map[string]Tool with a plain loop and no uniqueness check. When two tools share the same name, the last one silently wins — no error, no warning. This is particularly dangerous when tools come from multiple sources: user-supplied tools, plugin-loaded tools, and framework-injected tools.

Fix

Changed buildToolMap signature to return (map[string]Tool, error). Before inserting into the map, the function now checks for an existing key:

if _, exists := m[t.Name]; exists {
    return nil, fmt.Errorf("goai: duplicate tool name %q: tool names must be unique", t.Name)
}

All three callers (GenerateText, StreamText, GenerateObject) propagate the error naturally since they already return errors.

Tests

Added TestBuildToolMap_DuplicateToolName, TestGenerateText_DuplicateToolName, and TestStreamText_DuplicateToolName demonstrating the collision detection.

Related

See also: zendev-sh/zenflow# — same fix applied to zenflow's AgentRunner tool assembly.

@bclermont

Copy link
Copy Markdown
Contributor Author

The build fail for a reason that is unrelated to my changes

Silent last-wins behavior when two tools share the same name makes
debugging extremely difficult, especially when tools come from multiple
sources (user tools, plugins, framework-injected tools).

buildToolMap now returns an error on duplicate names:
  goai: duplicate tool name "foo": tool names must be unique

Fixes the collision by failing fast at tool registration time.
@vietanh85 vietanh85 force-pushed the fix/duplicate-tool-names branch from 6f14e97 to 2372b80 Compare June 7, 2026 22:03
@codecov

codecov Bot commented Jun 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

The duplicate-tool-name guard added to buildToolMap is propagated by
GenerateObject too, but only GenerateText and StreamText had a test for
it. Add TestGenerateObject_DuplicateToolName so the error branch in
GenerateObject is covered (fixes codecov/patch).
@vietanh85

Copy link
Copy Markdown
Contributor

/zenflow-review

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

zenflow review

Found logic and state management issues in the tool handling code.

Critical & High

  • generate.go:1506 - buildToolMap only checks for duplicate names among tools with Execute functions, deviating from Vercel AI SDK which validates all tool names (Vercel AI SDK is the reference) (idioms)

Medium

  • generate.go:1081 - StreamText missing StepStarting state transition on buildToolMap error path, inconsistent with other validation errors and potentially causing poller deadlocks (idioms)

request-changes

Review follow-ups from zenflow-review:
- buildToolMap now validates uniqueness across ALL tool names, not just
  those with an Execute function. The Vercel AI SDK (our reference) keys
  its tool set by name so collisions are impossible there; this matches
  that guarantee. The returned map still holds only executable tools.
- StreamText's buildToolMap error path now transitions the StateRef
  StepStarting->StepIdle like the other pre-loop validation errors, so a
  poller observing the state cannot deadlock.
@vietanh85

Copy link
Copy Markdown
Contributor

/zenflow-review

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

zenflow review

Found issues with deadlock-prone error handling in StreamText and API deviation in buildToolMap.

Critical & High

  • generate.go:1080 - StreamText returns early on error without transitioning StateRef to idle, causing pollers to deadlock and goroutine leaks (idioms, security)

Medium

  • generate.go:1507 - buildToolMap deviates from Vercel AI SDK by skipping tools with empty names; should return error instead of printing to os.Stderr (idioms)

request-changes

@vietanh85

Copy link
Copy Markdown
Contributor

/zenflow-review

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

zenflow review

Idiomatic issues were found regarding tool validation and error handling, but no security issues were detected.

Medium

  • object.go:526 - StreamObject is missing tool name uniqueness validation, deviating from GenerateText/StreamText and the Vercel AI SDK reference (Vercel AI SDK parity) (idioms)
  • generate.go:1502 - buildToolMap prints to os.Stderr and silently skips tools with missing names instead of returning an error, violating Go idioms for library error handling (idioms)

recommendation: comment

StreamObject passed tools straight to the provider without the duplicate
name check applied by GenerateText, StreamText and GenerateObject. Run the
same validation so all four entry points behave consistently.
@vietanh85

Copy link
Copy Markdown
Contributor

/zenflow-review

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

zenflow review

LGTM - no issues found by either reviewer.

@vietanh85

Copy link
Copy Markdown
Contributor

Reviewer returned LGTM. Summary of what changed beyond the original fix:

  • buildToolMap now validates uniqueness across all tool names, not just those with an Execute function, matching the Vercel AI SDK reference where the tool set is keyed by name.
  • The same validation is applied at all four entry points: GenerateText, StreamText, GenerateObject and StreamObject.
  • The StreamText error path now transitions the AgentState StepStarting to StepIdle like the other pre-loop validation errors, so pollers cannot deadlock.
  • Tests cover the duplicate cases for each entry point.

@vietanh85 vietanh85 merged commit df831fb into zendev-sh:main Jun 8, 2026
3 checks passed
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.

2 participants