Reviewed PR description, comments, commits, and changed files.
UnderstandIdentified and read linked issues.
UnderstandAnalyzed PR diff and relevant code files.
UnderstandApplied fixes and merged changes to main branch.
EditYou are given one or more GitHub PR URLs: https://github.com/badlogic/pi-mono/pull/807/changes
For each PR URL, do the following in order:
packages/*/CHANGELOG.md files. Report whether an entry exists. If missing, state that a changelog entry is required before merge and that you will add it if the user decides to merge. Follow the changelog format rules in AGENTS.md. Verify:
### Breaking Changes, ### Added, ### Fixed, etc.)Fixed foo ([#123](https://github.com/badlogic/pi-mono/pull/123) by [@user](https://github.com/user))### Breaking Changes, not just ### FixedOutput format per PR: PR: Changelog:
If no issues are found, say so under Bad and Ugly.
Let me read the current main branch file and check for linked issues:
I see the CHANGELOG on main already has a different entry under [Unreleased]. Let me check if there are any linked issues and check the documentation files:
Now let me check for any tests related to this functionality and verify the current state of the CHANGELOG:
PR: https://github.com/badlogic/pi-mono/pull/807
Changelog:
### Fixed in [Unreleased]### Fixed entry for issue #812, but the PR replaces it instead of appendingFixed foo ([#807](https://github.com/badlogic/pi-mono/pull/807) by [@joshp123](https://github.com/joshp123))Good:
todowrite, TodoWrite) will no longer trigger OAuth credential errorsccToolLookup.get(name.toLowerCase()) is the right approachls: "Ls" mapping (CC 2.x removed the ls tool)Bad:
### Fixed sectiontoClaudeCodeName / fromClaudeCodeName normalization (existing tests may cover via integration, but explicit unit tests would be valuable for this edge case)Ugly:
Questions or Assumptions:
ccExtraTools list was derived from CC 2.1.11. If Anthropic adds new tool names to Claude Code that collide with user custom tools, this will break again (acknowledged as acceptable risk in PR description)fromClaudeCodeName was not modified and continues to rely on matching against context.tools by lowercase name, which should still work correctly since the user's original tool name is preserved thereChange summary:
claudeCodeToolNames → piToolNames for clarityccExtraTools array with 11 additional CC 2.x builtin tool names not provided by pi: AskUserQuestion, EnterPlanMode, ExitPlanMode, KillShell, NotebookEdit, Skill, Task, TaskOutput, TodoWrite, WebFetch, WebSearchccToolLookup Map for case-insensitive normalizationtoClaudeCodeName to perform case-insensitive lookup: ccToolLookup.get(name.toLowerCase()) ?? namels: "Ls" mappingTests:
todowrite (lowercase) and verify no OAuth credential errors with Claude Max subscriptionDocumentation:
packages/coding-agent/README.md does not require modification (OAuth stealth mode is an internal implementation detail)packages/coding-agent/docs/*.md do not require modificationbut the implemnetation is fucking stupid, no? what we need to do is know all CC tools, if a pi tool matches ignoring case, we convert to the CC tool name which is send to anthropic, and upon receiving shit, we convert to the pi tool name, we just need to extend the list of tools we already have, no?
You're right. The implementation is overengineered.
What we actually need:
// All Claude Code 2.x tool names (canonical casing)const claudeCodeTools = [ "Read", "Write", "Edit", "Bash", "Grep", "Glob", "AskUserQuestion", "EnterPlanMode", "ExitPlanMode", "KillShell", "NotebookEdit", "Skill", "Task", "TaskOutput", "TodoWrite", "WebFetch", "WebSearch",];
// Pi aliases (pi uses "find", CC uses "Glob")const piAliases: Record<string, string> = { find: "Glob" };
const ccLookup = new Map([ ...claudeCodeTools.map(t => [t.toLowerCase(), t] as const), ...Object.entries(piAliases).map(([k, v]) => [k.toLowerCase(), v] as const),]);
const toClaudeCodeName = (name: string) => ccLookup.get(name.toLowerCase()) ?? name;The current PR splits this into:
piToolNames (pi tools mapped to CC names)ccExtraTools (CC tools pi doesn't provide)That's backwards. The primary concept is "CC tool names" and the secondary concept is "pi aliases for some of them". The current structure makes it look like there are two different kinds of tools when there's really just one list with one alias.
Functionally it works, but the code structure doesn't match the mental model. Should I suggest this simplification to the contributor, or do you want to just fix it during merge?
no, we don't need aliases, wtf where does that come from? if a pi tool matches a cc tool name ignoring csae, convert to cc tool name casing (camel case), and the reverse.
You're right, I'm overcomplicating it.
The only thing needed:
// All Claude Code 2.x tool names (canonical casing)const claudeCodeTools = [ "Read", "Write", "Edit", "Bash", "Grep", "Glob", "AskUserQuestion", "EnterPlanMode", "ExitPlanMode", "KillShell", "NotebookEdit", "Skill", "Task", "TaskOutput", "TodoWrite", "WebFetch", "WebSearch",];
const ccLookup = new Map(claudeCodeTools.map(t => [t.toLowerCase(), t]));
const toClaudeCodeName = (name: string) => ccLookup.get(name.toLowerCase()) ?? name;That's it. Tool name matches CC tool (ignoring case) → use CC casing. Doesn't match → leave it alone.
The existing find: "Glob" mapping is a separate concern (pi's "find" masquerading as CC's "Glob" for stealth). That's not about the OAuth collision fix. The PR conflates the two.
Should I write up this feedback for the contributor, or are you going to just fix it when merging?
the existing find -> glob is bullshit. set up a minimal test in ai package (follow th pattern you see there) to prove that.
also, we need the reverse as well, no? did you read the full @packages/ai/src/providers/anthropic.ts file? you should
I see now. The flow is:
toClaudeCodeName): Used in convertTools() and convertMessages() when sending tool definitions and tool_use history to AnthropicfromClaudeCodeName): Used when receiving tool_use events from Anthropic's streamThe current fromClaudeCodeName does a case-insensitive lookup against context.tools to find the original tool name. This is correct.
But here's the problem with find -> Glob:
"find" in context.toolstoClaudeCodeName("find") returns "Glob" → sent to Anthropic"Glob" toolfromClaudeCodeName("Glob", tools) looks for tool where name.toLowerCase() === "glob""find", so "find".toLowerCase() !== "glob""Glob""Glob" but no tool named "Glob"!The find -> Glob mapping is actually broken. Let me look at existing test patterns and create a test:
Now let me create a test to prove the issue. I'll create a test that:
todowrite) works with case-insensitive normalizationfind -> Glob mapping is broken (the round-trip fails)The tests prove exactly what we suspected:
Test results:
todowrite test FAILED - OAuth error "This credential is only authorized for use with Claude Code" - this is the bug the PR tries to fix. Current code doesn't normalize "todowrite" → "TodoWrite".
read test PASSED - Pi's built-in tools work because they're in the hardcoded map.
find -> Glob test FAILED - expected 'Glob' to be 'find' - proves the mapping is broken! The tool was sent as "Glob", came back as "Glob", and fromClaudeCodeName("Glob", tools) couldn't find a tool named "glob" (only "find" exists), so it returned "Glob".
Custom tool test PASSED - Tools not matching CC names pass through unchanged.
Summary:
find -> Glob mapping is demonstrably broken (test 3)The test file is at packages/ai/test/anthropic-tool-name-normalization.test.ts. Want me to:
anthropic.tsimplement the correct, concise fix, ensure test coverage is there, then return to me
Now let's run the tests:
All 4 tests pass. Now let's run the full check:
Let me verify the final state of the fix:
Done. Here's the summary:
The fix (13 lines of actual code):
// Claude Code 2.x tool names (canonical casing)const claudeCodeTools = [ "Read", "Write", "Edit", "Bash", "Grep", "Glob", "AskUserQuestion", "EnterPlanMode", "ExitPlanMode", "KillShell", "NotebookEdit", "Skill", "Task", "TaskOutput", "TodoWrite", "WebFetch", "WebSearch",];
const ccToolLookup = new Map(claudeCodeTools.map((t) => [t.toLowerCase(), t]));
const toClaudeCodeName = (name: string) => ccToolLookup.get(name.toLowerCase()) ?? name;What changed:
find: "Glob" mapping (and ls: "Ls")fromClaudeCodeName unchanged (already correct)Test coverage (test/anthropic-tool-name-normalization.test.ts):
| Test | Result |
|---|---|
todowrite → TodoWrite → todowrite | ✓ Pass |
read → Read → read | ✓ Pass |
find stays find (not mapped to Glob) | ✓ Pass |
| Custom tools pass through unchanged | ✓ Pass |
All checks pass:
npm run check ✓The PR's approach was overengineered (separate piToolNames + ccExtraTools merged into lookup). This is simpler: one list, one lookup.
commit and push, i'll comment on the pr myself
Done. Pushed to main:
a5f1016d fix(ai): normalize tool names case-insensitively against CC tool list6b4b4f4f Merge remote-tracking branch 'origin/main'Files committed:
packages/ai/src/providers/anthropic.tspackages/ai/test/anthropic-tool-name-normalization.test.ts// All Claude Code 2.x tool names (canonical casing)const claudeCodeTools = [ "Read", "Write", "Edit", "Bash", "Grep", "Glob", "AskUserQuestion", "EnterPlanMode", "ExitPlanMode", "KillShell", "NotebookEdit", "Skill", "Task", "TaskOutput", "TodoWrite", "WebFetch", "WebSearch",];
// Pi aliases (pi uses "find", CC uses "Glob")const piAliases: Record<string, string> = { find: "Glob" };
const ccLookup = new Map([ ...claudeCodeTools.map(t => [t.toLowerCase(), t] as const), ...Object.entries(piAliases).map(([k, v]) => [k.toLowerCase(), v] as const),]);
const toClaudeCodeName = (name: string) => ccLookup.get(name.toLowerCase()) ?? name;// All Claude Code 2.x tool names (canonical casing)const claudeCodeTools = [ "Read", "Write", "Edit", "Bash", "Grep", "Glob", "AskUserQuestion", "EnterPlanMode", "ExitPlanMode", "KillShell", "NotebookEdit", "Skill", "Task", "TaskOutput", "TodoWrite", "WebFetch", "WebSearch",];
const ccLookup = new Map(claudeCodeTools.map(t => [t.toLowerCase(), t]));
const toClaudeCodeName = (name: string) => ccLookup.get(name.toLowerCase()) ?? name;// Claude Code 2.x tool names (canonical casing)const claudeCodeTools = [ "Read", "Write", "Edit", "Bash", "Grep", "Glob", "AskUserQuestion", "EnterPlanMode", "ExitPlanMode", "KillShell", "NotebookEdit", "Skill", "Task", "TaskOutput", "TodoWrite", "WebFetch", "WebSearch",];
const ccToolLookup = new Map(claudeCodeTools.map((t) => [t.toLowerCase(), t]));
const toClaudeCodeName = (name: string) => ccToolLookup.get(name.toLowerCase()) ?? name;a5f1016d fix(ai): normalize tool names case-insensitively against CC tool list6b4b4f4f Merge remote-tracking branch 'origin/main'