Skip to content

fix(web): preserve source revisions in chat citation links#1205

Merged
brendan-kellam merged 2 commits into
mainfrom
brendan/fix-chat-citation-revisions
May 15, 2026
Merged

fix(web): preserve source revisions in chat citation links#1205
brendan-kellam merged 2 commits into
mainfrom
brendan/fix-chat-citation-revisions

Conversation

@brendan-kellam
Copy link
Copy Markdown
Contributor

@brendan-kellam brendan-kellam commented May 15, 2026

Summary

  • convertLLMOutputToPortableMarkdown was not passing revisionName to getBrowsePath, 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 the Source data parts. Plumbed file sources through both callsites (AnswerCard via chatThreadListItem, and askCodebase by pulling data-source parts off finalMessages) so the conversion can resolve each reference to its source and use that source's revision in the URL.
  • Tightened get_diff source emission. Previously every modified file emitted two sources at the same repo+path differing only by revision (base + head). Since the reference resolver does Array.find on repo/path only, 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

`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>
@github-actions

This comment has been minimized.

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

coderabbitai Bot commented May 15, 2026

Walkthrough

File citations from the get_diff tool are now reliably citable in chat answers by threading source metadata through the markdown conversion pipeline. The getDiff tool builds a sources array from diff files, askCodebase extracts file sources from message parts, and the markdown converter resolves @file... references using those sources to generate accurate link text and revision-aware browse URLs.

Changes

Source-aware file reference resolution in chat answers

Layer / File(s) Summary
File reference resolution with sources
packages/web/src/features/chat/utils.ts
convertLLMOutputToPortableMarkdown now accepts a sources parameter, constructs FileReference objects from parsed @file... tokens, resolves them via tryResolveFileReference, and uses the resolved source's name and revision to build enhanced browse links.
Source extraction from diff tool
packages/web/src/features/tools/getDiff.ts
get_diff tool builds a sources array from response files, categorizing each as deleted (base), renamed (both base and head), or added/modified (head), with repo, path, display name, and revision metadata.
Source collection in MCP flow
packages/web/src/features/mcp/askCodebase.ts
askCodebase extracts data-source message parts, filters to file-based sources, and passes the resulting fileSources list to convertLLMOutputToPortableMarkdown alongside the answer text and base URL.
Component integration of sources
packages/web/src/features/chat/components/chatThread/answerCard.tsx, packages/web/src/features/chat/components/chatThread/chatThreadListItem.tsx
AnswerCard receives a sources prop and passes it to convertLLMOutputToPortableMarkdown when copying the answer. ChatThreadListItem provides referencedFileSources as the sources prop to AnswerCard.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

  • sourcebot-dev/sourcebot#1142: Both PRs touch packages/web/src/features/tools/getDiff.ts, where the retrieved PR adds the get_diff tool implementation while this PR extends its returned metadata to include citation-able sources derived from response.files.
  • sourcebot-dev/sourcebot#393: This PR extends convertLLMOutputToPortableMarkdown and its @file resolution via createFileReference/tryResolveFileReference to use FileSource metadata for citable links, building on the retrieved PR's utils.ts refactor that changes file reference representation from fileName to repo/path.
  • sourcebot-dev/sourcebot#847: Both PRs modify convertLLMOutputToPortableMarkdown in packages/web/src/features/chat/utils.ts and its call sites to change how @file references are rendered—this PR extends the mechanism by adding sources/revision resolution, building on the retrieved PR's work to add baseUrl for full browse URLs.

Suggested reviewers

  • msukkari
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'fix(web): preserve source revisions in chat citation links' directly and accurately reflects the main objectives—fixing revision handling in citation links across the codebase by threading file source information through multiple components and correcting source emission from get_diff.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch brendan/fix-chat-citation-revisions

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.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/web/src/features/chat/utils.ts (1)

377-382: 💤 Low value

Consider more precise matching logic.

The current implementation uses endsWith for 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

📥 Commits

Reviewing files that changed from the base of the PR and between d5ad64c and e5ef1c5.

📒 Files selected for processing (6)
  • CHANGELOG.md
  • packages/web/src/features/chat/components/chatThread/answerCard.tsx
  • packages/web/src/features/chat/components/chatThread/chatThreadListItem.tsx
  • packages/web/src/features/chat/utils.ts
  • packages/web/src/features/mcp/askCodebase.ts
  • packages/web/src/features/tools/getDiff.ts

Comment thread packages/web/src/features/mcp/askCodebase.ts
@brendan-kellam brendan-kellam merged commit b65db72 into main May 15, 2026
11 checks passed
@brendan-kellam brendan-kellam deleted the brendan/fix-chat-citation-revisions branch May 15, 2026 23:03
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.

1 participant