uttrflow / uttrflow/uttrflow-swift

Unpinning a saved clip can delete its only durable copy when the history write fails

Open
#769 0 comments 0 reactions 0 assignees View on GitHub
area:clipboard bug P0
Dominant language
Swift
Stars
4
Forks
17
Avg merge
3h 32m
Merged PRs (30d)
277

Description

## Problem

`ClipboardStore.save` removes a clip from `saved.v1.json` before persisting its destination history file. If that second write fails, the operation throws but the original saved copy is already gone. Quitting before another successful write loses the clip. This affects unpinning a clip with no alias or collection, and other transitions out of the saved pool.

## Evidence

Reviewed `main` at `8d93f5b8574f3c7b8c30086d9c817bf7f5d4827d`. Reproduced against the unchanged production source in an isolated Swift package on macOS, with fake transport or temporary stores as appropriate. These checks assert the observed defect; they are not a claim that the full app test suite passed.

1. Record a pinned text clip in a temporary store; confirm it is in `saved.v1.json`.
2. Make the history destination unwritable. The reproduction creates a directory at the otherwise absent `clipboard.v1.json` path to deterministically fail the write, without changing directory permissions globally.
3. Call `setPinned(false, of: id, keeping: retention)`.
4. It throws `couldNotWrite`, but `saved.v1.json` has been removed.
5. Reopen `ClipboardStore`: the clip is absent (`FAILED_UNPIN_AFTER_REOPEN []`).

A disk-full failure while writing the destination has the same two-write ordering problem. The reproduced result is local-store data loss, not just a missing error notice.

## Relevant code

- [Sources/UttrflowClipboard/ClipboardStore.swift:451](https://github.com/uttrflow/uttrflow-swift/blob/8d93f5b8574f3c7b8c30086d9c817bf7f5d4827d/Sources/UttrflowClipboard/ClipboardStore.swift#L451)
- [Sources/UttrflowClipboard/ClipboardStore.swift:138](https://github.com/uttrflow/uttrflow-swift/blob/8d93f5b8574f3c7b8c30086d9c817bf7f5d4827d/Sources/UttrflowClipboard/ClipboardStore.swift#L138)

## Fix and acceptance criteria

Make transfers between the two files recoverable: persist the destination before deleting the source, or use a transaction/journal with recovery and identity deduplication. A failed unpin must leave at least one durable copy, and reopening must recover exactly one clip. Add fault-injection coverage at each write boundary and for both transfer directions.

## Existing work checked

#739 asks for write-error branch coverage, and #581 asks for visible pinning errors. Neither describes loss of the original saved file after the first half of a transfer succeeds.

Contributor guide

Open the contributing guide

Research direction

Start in Sources/UttrflowClipboard/ClipboardStore.swift around lines 138 and 451, tracing the saved-to-history and reverse transfer paths. Reproduce the failed destination write described in the issue, then add fault-injection coverage at each write boundary for both directions. Done means a failed transfer preserves at least one durable copy and reopening recovers exactly one clip.

Written by the indexing model from the issue text.

Assessment

Tech stack
swift
Domain
databases, desktop
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.