Skip to content

Attach tray: re-attaching the same file stops stacking duplicate chips - #40

Merged
firish merged 3 commits into
firish:mainfrom
DaveTseng2019:fix/attach-dedupe-source
Aug 20, 2026
Merged

Attach tray: re-attaching the same file stops stacking duplicate chips#40
firish merged 3 commits into
firish:mainfrom
DaveTseng2019:fix/attach-dedupe-source

Conversation

@DaveTseng2019

Copy link
Copy Markdown
Contributor

The bug

Drop or paste a file that lives outside the workspace, then do it again. You get two chips:

foo.png    (≈1.2k tok)
foo-2.png  (≈1.2k tok)

Same file, same bytes, two copies in .claude\attachments\, two at_mentioned sends, two of the 20 tray slots gone. Do it a few times while iterating on a screenshot outside the repo and the tray fills with clones of one file.

The tray already dedupes — but only references (WasCopied=false). Out-of-workspace files come in as staged copies, and the comment on that path said copies are always distinct files so they never match. True for a pasted screenshot; not true for a file the user handed us twice.

The fix

Match on the original path + its last-write time (FindBySource), checked before any copying happens. A repeat re-mentions the chip that already exists — exactly what clicking the chip does.

The timestamp is deliberately part of the key: a staged copy froze the old bytes, so a file edited since it was staged must still come in as a new attachment. That is usually the reason someone attaches the same path a second time.

Placed ahead of the BMP branch as well, so re-dropping a .bmp finds the PNG transcoded the first time instead of transcoding it again.

Unchanged on purpose:

  • Pasted screenshots and composer text still never dedupe. No source file, so each one is genuinely new content.
  • Ranged mentions stay separate attachments. Program.cs#L10-20 and #L30-40 are different things.
  • In-workspace references behave as before (they now short-circuit one step earlier, same outcome).

Verification

Release build clean. Manually: drop a desktop file on the panel twice → one chip, two @-mentioned lines in the feed. Save the file, drop again → a second chip appears, which is the intended escape hatch.

CLAUDE.md's attachment paragraph is updated — the old sentence ("copies never dedupe") is no longer accurate.

🤖 Generated with Claude Code

DaveTseng2019 and others added 3 commits August 16, 2026 06:50
Dropping or pasting a file that lives OUTSIDE the workspace stages a copy, and
the tray's dedupe only matched references (WasCopied=false). So attaching the
same file twice produced foo.png and foo-2.png: two chips, identical content,
two mentions, both counting against the 20-item cap.

Match on the ORIGINAL path plus its last-write time, checked before any copying
(FindBySource), so a repeat re-mentions the chip that is already there - the
same thing clicking the chip does. The timestamp is part of the key on purpose:
a staged copy froze the old bytes, so a file edited since must still come in as
a new attachment. It sits ahead of the BMP branch too, so re-dropping a .bmp
finds the PNG we transcoded the first time.

Pasted screenshots and composer text still never dedupe - they have no source
file, so each one is genuinely new content.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Reviewing the capture path for this PR (does a screenshot's empty
SourcePath let FindBySource match a real file? no - it cannot) turned
up something I left behind in firish#36: StageCapturePng still had the old
inline trim, `while (Items.Count > MaxItems) Items.RemoveAt(0)`, so a
capture arriving at a full tray could evict a still-pending mention -
exactly the invariant AddItem exists to protect. It now uses AddItem
like every other add, and the empty SourcePath is documented as
deliberate rather than incidental.

Behaviour verified against the built assemblies in a temp workspace:
an out-of-workspace file dropped twice = one chip; edited and dropped
again = a second chip (the escape hatch); an in-workspace file twice =
one chip, still WasCopied=false; the same image pasted twice = two
chips, since contentless pastes have no source to match on.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@firish

firish commented Aug 20, 2026

Copy link
Copy Markdown
Owner

Merging - this is a genuine data bug and the diagnosis is exactly right, including the part where my comment on that path was the misleading bit. "Copies are always distinct files" is true of a pasted screenshot and false of a file handed to us twice, and I wrote it without noticing the difference.

Two things I want to credit specifically. The timestamp in the key is what makes this correct rather than merely convenient: a staged copy froze the old bytes, so a file edited since staging has to come in fresh, and that edit is usually the whole reason someone attaches the same path again. And placing the check ahead of the BMP branch means a re-dropped .bmp finds the PNG you transcoded the first time instead of transcoding it again - easy to miss, and it would have been a subtle waste.

Verified against the built assemblies in a throwaway workspace rather than by reading: outside file dropped twice = one chip; edited then dropped again = a second chip; in-workspace file twice = one chip, still WasCopied=false so the source is never deletable; the same image pasted twice = two chips, since contentless pastes have no source to match on. All five as designed.

One maintainer tweak on the branch, and it came out of reviewing your change: checking whether a capture's empty SourcePath could ever match a real file (it cannot) showed that StageCapturePng still used the old inline trim from before #36, so a screenshot arriving at a full tray could evict a still-pending mention - the exact invariant AddItem exists to protect. Routed through AddItem, and the empty SourcePath is now documented as deliberate.

Thanks Dave - and thanks for correcting the CLAUDE.md sentence rather than leaving it wrong.

@firish
firish merged commit 021d9cf into firish:main Aug 20, 2026
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