2c9a3fd05dad
fix: PCH-01-021 gate clipboard alarm clear on the clear-owed flag (CLEAR_HASH_KEY) — stale alarm on a cold worker skips offscreen churn; dead bookkeeping is now load-bearing (v3.8.1)
- time
- 2026-06-10 20:20 UTC · 4 months ago
- machine
- koh2
- agent
- claude-opus-4-8
- session
- pch-01 fix integration
- parent
- 3867c5eae0a1
↓ peach-browser-2c9a3fd0.tar.gzatlas at this save
files changed2 files
src/background/clipboard.test.ts+25 −0src/background/clipboard.ts+18 −1src/background/clipboard.test.ts+25 −0
@@ -178,6 +178,8 @@178178 setupClipboardClearAlarm();179179 const clipListener = alarmListeners.at(-1)!;180180 181+ // PCH-01-021: the alarm only clears when a clear is still owed.182+ session["clipboard-clear-hash"] = "owed";181183 clipListener({ name: CLIPBOARD_CLEAR_ALARM });182184 await flushMicrotasks();183185 @@ -206,12 +208,33 @@206208 setupClipboardClearAlarm();207209 const clipListener = alarmListeners.at(-1)!;208210 211+ // PCH-01-021: the alarm only clears when a clear is still owed.212+ session["clipboard-clear-hash"] = "owed";209213 clipListener({ name: CLIPBOARD_CLEAR_ALARM });210214 await flushMicrotasks();211215 212216 expect(clipboardWriteText).toHaveBeenCalledWith("");213217 });214218 219+ // PCH-01-021: a stale alarm with no clear owed must skip the clear and drop220+ // the alarm, avoiding pointless offscreen-document churn on a cold worker.221+ it("PCH-01-021: alarm with no clear-owed flag skips the clear and drops the alarm", async () => {222+ const { setupClipboardClearAlarm, CLIPBOARD_CLEAR_ALARM } = await import(223+ "./clipboard"224+ );225+ setupClipboardClearAlarm();226+ const clipListener = alarmListeners.at(-1)!;227+228+ // Empty session → nothing owed.229+ delete session["clipboard-clear-hash"];230+ alarmsClear.mockClear();231+ clipListener({ name: CLIPBOARD_CLEAR_ALARM });232+ await flushMicrotasks();233+234+ expect(clipboardWriteText).not.toHaveBeenCalled();235+ expect(alarmsClear).toHaveBeenCalledWith(CLIPBOARD_CLEAR_ALARM);236+ });237+215238 it("HV-2: a second copy clears the previous alarm before re-arming", async () => {216239 const { copyToClipboard, CLIPBOARD_CLEAR_ALARM } = await import(217240 "./clipboard"@@ -251,6 +274,8 @@251274 const clipListener = alarmListeners.at(-1)!;252275 alarmsCreate.mockClear();253276 277+ // PCH-01-021: a clear is owed (hash present), so the alarm proceeds to clear.278+ session["clipboard-clear-hash"] = "owed";254279 clipListener({ name: CLIPBOARD_CLEAR_ALARM });255280 await flushMicrotasks();256281 src/background/clipboard.ts+18 −1
@@ -179,6 +179,23 @@179179 export function setupClipboardClearAlarm(): void {180180 chrome.alarms.onAlarm.addListener((alarm) => {181181 if (alarm.name !== CLIPBOARD_CLEAR_ALARM) return;182- void clearClipboardNow();182+ // PCH-01-021: only clear if a clear is still owed. CLEAR_HASH_KEY is set on183+ // copy and removed once a clear succeeds; its absence means the fast-path184+ // setTimeout (or a prior alarm) already cleared, so a cold worker woken by a185+ // stale alarm can skip the offscreen-document churn.186+ void (async () => {187+ try {188+ const stored = await chrome.storage.session.get(CLEAR_HASH_KEY);189+ if (stored?.[CLEAR_HASH_KEY] === undefined) {190+ // Nothing owed — drop the stale alarm and return.191+ await chrome.alarms.clear(CLIPBOARD_CLEAR_ALARM).catch(() => false);192+ return;193+ }194+ } catch {195+ // session.get failed — fall through and clear anyway. Erring toward196+ // clearing keeps the secret-must-not-persist invariant.197+ }198+ await clearClipboardNow();199+ })();183200 });184201 }