{"record":{"id":"f602195c199fea54","repo":"Hmbown/CodeWhale","slug":"a-saved-trace-identifier-collided-try-saving-again","errorCode":null,"errorMessage":"A saved trace identifier collided. Try saving again.","messagePattern":"A saved trace identifier collided\\. Try saving again\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"pet/src/ui/storage.ts","lineNumber":83,"sourceCode":"      const fail = (error: Error) => { failure ??= error; try { tx.abort(); } catch { reject(failure); } };\n      tx.oncomplete = () => failure ? reject(failure) : resolve(result);\n      tx.onerror = () => { failure ??= problem(tx.error); };\n      tx.onabort = () => reject(failure ?? problem(tx.error));\n      try { action(tx.objectStore('traces'), tx.objectStore('summaries'), value => { result = value; }, fail, tx.objectStore('habitats')); }\n      catch (e) { fail(e as Error); }\n    });\n  }\n  async save(trace: Trace, expected?: SavedReference): Promise<SavedReference> {\n    await this.open(); // Origin error before secure-context-only randomUUID.\n    const key = expected?.key ?? crypto.randomUUID();\n    return this.transaction('readwrite', (traces, metas, done, fail) => {\n      const req = traces.get(key);\n      req.onsuccess = () => {\n        try {\n          const previous: SavedTrace | undefined = req.result;\n          if (expected && !previous) throw missing();\n          if (expected && previous!.savedAt !== expected.savedAt) throw changed();\n          if (!expected && previous) throw new Error('A saved trace identifier collided. Try saving again.');\n          const savedAt = this.nextTime(previous?.savedAt), item: SavedTrace = { key, savedAt, trace };\n          traces.put(item); metas.put(summary(item)); done({ key, savedAt });\n        } catch (e) { fail(e as Error); }\n      };\n    });\n  }\n  private nextTime(previous?: string): string {\n    const last = previous ? Date.parse(previous) : 0;\n    return new Date(Math.max(Date.now(), Number.isFinite(last) ? last + 1 : 0)).toISOString();\n  }\n  async list(): Promise<SavedSummary[]> {\n    return this.transaction('readonly', (_traces, metas, done) => {\n      const req = metas.getAll();\n      req.onsuccess = () => done((req.result as SavedSummary[]).sort((a, b) => b.savedAt.localeCompare(a.savedAt)));\n    });\n  }\n  async getEntry(key: string): Promise<SavedTrace> {\n    return this.transaction('readonly', (traces, _metas, done, fail) => {","sourceCodeStart":65,"sourceCodeEnd":101,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/pet/src/ui/storage.ts#L65-L101","documentation":"Thrown inside the IndexedDB `traces.get(key)` success callback when saving a new trace (`expected` is undefined) but an existing record already occupies the generated key. The key is time-derived (`nextTime` disambiguates only against loaded records), so a collision with an unseen record is possible; the save is aborted and the caller told to retry so a fresh key is generated.","triggerScenarios":"Calling `save` with no `expected` value while another record already exists with the same generated key — typically after importing/restoring records the in-memory time base does not know about, or two concurrent saves computing the same `savedAt`.","commonSituations":"Rapid consecutive saves, multiple tabs sharing the same IndexedDB, importing saved traces then saving new ones whose derived timestamps collide.","solutions":["Retry the save as the message suggests — `nextTime` will pick a new key on the attempt.","Load existing trace metadata before saving so `nextTime` sees prior `savedAt` values.","Add a random component or monotonic counter to key generation to make collisions practically impossible."],"exampleFix":"// before\nconst savedAt = this.nextTime(previous?.savedAt); // may still collide\n// after\nconst savedAt = this.nextTime(previous?.savedAt);\nif (previous) return retryWithNewKey(); // regenerate key and re-run put","handlingStrategy":"retry","validationCode":"const existing = await library.listTraceMetas();\n// ensure nextTime is seeded with all known savedAt values before saving","typeGuard":"null","tryCatchPattern":"try { await library.save(key, trace); } catch (e) { if (e.message.includes('collided')) await library.save(newKey(), trace); }","preventionTips":["Load all existing trace metadata before generating new keys.","Use one writer tab, or coordinate saves across tabs.","Add a random suffix to generated keys to make collisions unlikely."],"tags":["indexeddb","id-collision","concurrency"],"backgroundTag":"file-already-exists","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-22T01:17:13.364Z"}