fix(web): preserve source revisions in chat citation links#1205
Conversation
`convertLLMOutputToPortableMarkdown` was not passing `revisionName` to `getBrowsePath`, so every copied citation link silently resolved to the repo's default branch — even when the source was attached at a non-HEAD revision. Plumb file sources through both callsites so the conversion can resolve each reference to its source and use that source's revision. Also tighten `get_diff` source emission: one source per path at head for added/modified files, base for deletes, both sides for renames. The old behavior emitted a duplicate unreachable source at base for every modified file that the reference resolver would have picked first. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This comment has been minimized.
This comment has been minimized.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
WalkthroughFile citations from the ChangesSource-aware file reference resolution in chat answers
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
packages/web/src/features/chat/utils.ts (1)
377-382: 💤 Low valueConsider more precise matching logic.
The current implementation uses
endsWithfor both repo and path matching, which could lead to ambiguous matches when multiple files share the same basename or repos share name suffixes. For example,source.path.endsWith("bar.ts")would match both"foo/bar.ts"and"baz/bar.ts".While this fuzzy matching may be intentional to handle variations in how the LLM references files, consider whether exact matching (or at least full path comparison) would be more robust.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/web/src/features/chat/utils.ts` around lines 377 - 382, The tryResolveFileReference function currently uses endsWith for both source.repo and source.path which yields ambiguous matches; update the matching logic in tryResolveFileReference (and consider FileReference/FileSource shapes) to first attempt exact/full-path matches (source.repo === reference.repo and source.path === reference.path) and only if none found fall back to a deliberate, documented fuzzy strategy (e.g., basename equality or suffix match) to avoid accidental collisions; ensure you reference the function name tryResolveFileReference and the types FileReference and FileSource when making the change and keep the fallback behavior explicit and tested.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/web/src/features/mcp/askCodebase.ts`:
- Around line 199-204: The chained flatMap/filter/map loses TypeScript's
discriminated-union narrowing for the file sources; add an explicit type guard
(e.g., declare isFileSource = (s: Source): s is FileSource => s.type === 'file')
and use that predicate in the final filter when building fileSources from
finalMessages so fileSources is correctly typed before passing into
convertLLMOutputToPortableMarkdown; ensure the predicate references the same
Source/FileSource types used elsewhere so the compiler can narrow the type.
---
Nitpick comments:
In `@packages/web/src/features/chat/utils.ts`:
- Around line 377-382: The tryResolveFileReference function currently uses
endsWith for both source.repo and source.path which yields ambiguous matches;
update the matching logic in tryResolveFileReference (and consider
FileReference/FileSource shapes) to first attempt exact/full-path matches
(source.repo === reference.repo and source.path === reference.path) and only if
none found fall back to a deliberate, documented fuzzy strategy (e.g., basename
equality or suffix match) to avoid accidental collisions; ensure you reference
the function name tryResolveFileReference and the types FileReference and
FileSource when making the change and keep the fallback behavior explicit and
tested.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 627c51e7-16ff-44bd-97f0-0eb64826c793
📒 Files selected for processing (6)
CHANGELOG.mdpackages/web/src/features/chat/components/chatThread/answerCard.tsxpackages/web/src/features/chat/components/chatThread/chatThreadListItem.tsxpackages/web/src/features/chat/utils.tspackages/web/src/features/mcp/askCodebase.tspackages/web/src/features/tools/getDiff.ts
Summary
convertLLMOutputToPortableMarkdownwas not passingrevisionNametogetBrowsePath, so every copied citation link silently fell back to the repo's default branch even when the source was attached at a non-HEAD revision. The reference grammar (@file:{repo::path}) doesn't carry a revision; the revision lives on theSourcedata parts. Plumbed file sources through both callsites (AnswerCardviachatThreadListItem, andaskCodebaseby pullingdata-sourceparts offfinalMessages) so the conversion can resolve each reference to its source and use that source's revision in the URL.get_diffsource emission. Previously every modified file emitted two sources at the samerepo+pathdiffering only by revision (base + head). Since the reference resolver doesArray.findonrepo/pathonly, the base entry was unreachable for citations AND won the resolver's first-match — meaning click-throughs landed on the pre-change version. New behavior: emit one source per path at head for added/modified files, base for deletes, both sides for renames (paths differ so both are individually addressable).🤖 Generated with Claude Code