fix(capture): scope the own-staging guard to the image file-flavor only - #72
Merged
Conversation
Recovering a *file* from history was silent and didn't sync. Two unrelated
things staged into `DATA_DIR/paste`:
paste_path() a recovered file, copied out under its original name so the
paste isn't named after its sha256 — a genuine user clip
_stage_image() a throwaway backing file for an image also offered as a file,
whose uri-list bounces back through wl-paste --watch
`_is_own_staging` matched the whole directory, so it dropped both. With no file
path surviving the filter and no image flavor on the clipboard, capture_current()
fell through to `new_id = None`; `_cmd_store` then sent no `_current`, no
`refresh` and no `_broadcast`, and never reached the `sound.play()` inside
`if new_id is not None`. Meanwhile the panel's storage.touch() still moved the
entry to the front, so the recover looked like it had worked.
Measured: entry 8939 recovered at 22:10:13, `.last_sound` still holding 8946
from 22:10:00 — capture ran and stored nothing.
The echo it was written for is unaffected: _stage_image now writes to
FLAVOR_DIR (`paste/.image-flavor/`) and the guard matches only that. Both
directories are named in config.py rather than spelled out at three call sites,
which is what let them drift into one.
Re-capturing a recovered file is harmless: add_file_from_path dedupes on the
content hash and returns the existing id, so it bumps created_at rather than
filing a duplicate — the sound and the broadcast are exactly what we want back.
scripts/capture_staging_test.py (new, in CI) pins the two apart, including the
cross-module case that actually broke — paste_path()'s output must survive
capture's filter. It fails on the old guard with "recovered file must be
captured, not ignored".
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This was referenced Jul 31, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Symptom
Recovering a file from history played no copy sound and never synced to the paired device.
Cause
Two unrelated things staged into
DATA_DIR/paste:storage.paste_path()_stage_image()wl-paste --watch_is_own_stagingmatched the whole directory, so it dropped both:With no file path surviving and no
image/pngon the clipboard,capture_current()fell through tonew_id = None._cmd_store(cli.py:77-85) then sent no_current, norefresh, no_broadcast, and never reached thesound.play()insideif new_id is not None.The panel's
storage.touch()still moved the entry to the front, so the recover looked like it worked. Measured: entry 8939 recovered at 22:10:13 while.last_soundstill held8946from 22:10:00 — capture ran and stored nothing.Fix
_stage_imagewrites toFLAVOR_DIR(paste/.image-flavor/); the guard matches only that. The echo it exists for is still suppressed; file recovers now capture normally, so the sound and the broadcast come back.Both directories are named in
config.pyinstead of being spelled out at three call sites — that duplication is what let them drift into one.Re-capturing a recovered file is harmless:
add_file_from_pathdedupes on the content hash and returns the existing id (bumpingcreated_at), so no duplicate entry.Tests
scripts/capture_staging_test.py(new, wired into CI) pins the two apart — including the cross-module case that actually broke: whateverpaste_path()hands the panel must survive capture's filter. Against the old guard:Full suite green locally: selector prototypes, mac pasteboard, capture staging, sync core, device-id drift, sync delivery.
Note
Existing files already staged in
paste/from before this change are unaffected — they're only ever overwritten per recover. Nothing migrates and nothing needs cleaning up.