Actively on it - so sorry!
[Bug]: Mac Nightly: projects and chats appear missing after this morning's update#22216
Before submitting
- I searched existing issues and did not find a duplicate of this Nightly update failure. Related issues are listed below.
- I included enough detail to reproduce or investigate the problem.
Area
apps/desktop
Steps to reproduce
Observed on an existing Mac desktop installation; a clean-profile reproduction has not been established:
- Have existing projects and chat history in T3 Code.
- Update the Mac desktop app this morning, 20 October 2026.
- Reopen the app.
Expected behavior
Existing projects and chats remain accessible after an update.
Actual behavior
All my projects and chats appeared to be missing after the update, making it look as though I had lost all my data.
Investigation identified a connection-catalog decryption failure. The history was still present in the database; the app could not load access to it.
This is a Nightly report. #20906 describes a related encryption-identity problem, but explicitly excludes Nightly and is now closed as fixed. Please check whether this is a follow-up regression from that fix, rather than assuming its original channel scope still holds.
Impact
Blocks work completely
Version or commit
T3 Code Nightly. The exact build is omitted from this public report.
Environment
Mac desktop app.
Logs or stack traces
Generic error, without local paths or full logs:
| 1 | DesktopConnectionCatalogStoreProtectionError: |
| 2 | Desktop connection catalog protection failed during decrypt-catalog. |
| 3 | |
| 4 | Caused by: |
| 5 | ElectronSafeStorageDecryptError: |
| 6 | Electron safe storage failed to decrypt a string. |
The exact encryption-key mismatch has not been established.
Screenshots, recordings, or supporting files
No private files or full logs attached.
Workaround
Access was restored locally after preserving the failing catalog and resetting the catalog state. This is a workaround, not a confirmed fix for the underlying decryption failure.
Related: #20906 and #20907 (encryption-identity change), #8341 and #15001 (similar Windows failures).
Revert back to a previous version for now.
Second Mac Nightly install with the same failure. I think this pins down the key mismatch.
Diagnosis
- Before today's update, this Mac's Nightly safeStorage key was the Keychain item
t3code Safe Storage(accountt3code Key). - #20907 (3819eb367a) now calls
app.setName(resolveDesktopSafeStorageName(...))before ready. For nightly builds that returnsT3 Code (Nightly). - On the first launch of the new build, Electron created a new Keychain item,
T3 Code (Nightly) Safe Storage, at 08:12:24Z. The firstdecrypt-catalogfailure is in the same second. decryptWithPreviousNameonly runs when the current name isT3 Code (Alpha), and it only tries theT3 Code (Beta)key. Nightly has no fallback, so the oldt3codekey is never tried.- #20906 assumed Nightly was safe because it "has always been packaged as
T3 Code (Nightly)". On this Mac, noT3 Code (Nightly) Safe Storageitem existed before today. The only other T3 item ist3code Safe Storage, so that is very likely the key the old catalog uses. I have not yet confirmed this by decrypting with it.
Suggested fix
On macOS/Linux nightly, when the catalog fails to decrypt, also try the t3code Safe Storage / t3code Key key, then re-encrypt with the new key, the same way the Beta fallback works.
Version
Last good: 0.1.1-nightly.20261020.3386. First bad: 0.1.1-nightly.20261020.3399.
Environment
macOS (Darwin 27.0.0), Apple Silicon, desktop app with its local server.
Evidence
| 1 | Keychain (names and creation times only): |
| 2 | t3code Safe Storage / t3code Key created 2026-06-25 |
| 3 | T3 Code (Nightly) Safe Storage / T3 Code (Nightly) Key created 2026-10-20T08:12:24Z |
| 4 | |
| 5 | desktop.trace.ndjson: first decrypt-catalog failure at 2026-10-20T08:12:24Z, none in older rotated logs. |
| 6 | connection-catalog.json was last written at 07:40Z by build 3386. Its ciphertext starts with "v10". |
| 7 | |
| 8 | DesktopConnectionCatalogStoreProtectionError: Desktop connection catalog protection failed during decrypt-catalog at ~/.t3/userdata/connection-catalog.json. |
| 9 | at desktop.connectionCatalogStore.recoverPreviousIdentityCatalog |
| 10 | [cause]: ElectronSafeStorageDecryptError: Electron safe storage failed to decrypt a string. |
| 11 | [cause]: Error: Error while decrypting the ciphertext provided to safeStorage.decryptString. |
Workaround
Not run yet: quit the app, decrypt the catalog with the t3code Safe Storage key, and re-encrypt it with the T3 Code (Nightly) Safe Storage key. I'll report back if it works.
Filed by: Claude Code (claude-opus-5-5), following the t3 triage playbook.
Warning
Update: #20907 was reverted in #22219. The next Nightly build goes back to the old t3code key.
- If you have not run the script yet, do not run it. Wait for the next Nightly build.
- Do not run this script after you update to that build. It would break your saved connections again.
- If you ran the script and then updated: quit T3 Code (Nightly) and sign in to T3 Connect again first if needed. Then copy the backup the script made (
connection-catalog.json.bak-<number>in~/.t3/userdata) back overconnection-catalog.json. That backup uses the old key. - Everyone who launched build
.3399must sign in to T3 Connect again: the Clerk session (clerk-tokens.json) uses the same key and is lost.
Confirmed on a second Mac (desktop, Nightly 0.1.1-nightly.20261020.3399, macOS arm64). This is a regression from #20907. The data is not lost: the local server and database are fine. Only the saved connection catalog is unreadable, so no local or remote environment loads.
Cause
- Before #20907, Nightly used the Electron safeStorage name
t3code, so its key was the Keychain itemt3code Safe Storage. - #20907 sets the name to
T3 Code (Nightly)before ready. On the first launch of the new build, Chromium created a newT3 Code (Nightly) Safe Storagekey. Its Keychain creation time is the same second as that launch. DesktopSafeStorageIdentity.decryptWithPreviousNameruns only when the name isT3 Code (Alpha), and it only tries the Beta key. So Nightly has no recovery path.- Checked: the existing
connection-catalog.jsondecrypts with thet3codekey to a valid catalog, and fails with theT3 Code (Nightly)key.
Workaround (macOS)
This script decrypts the catalog with the old key and encrypts it again with the new Nightly key. It makes a backup first and changes nothing unless an old key works. It needs no installs: it uses the app's bundled Electron as Node and the built-in security tool.
- Quit T3 Code (Nightly) with Cmd+Q.
- Save the script below as
fix-t3-nightly.sh. Do not paste it directly into your shell. - Run
bash fix-t3-nightly.sh. - Click Allow on each Keychain prompt.
- Open T3 Code (Nightly) again.
fix-t3-nightly.sh
| 1 | #!/bin/bash |
| 2 | # Restores saved connections (local projects + remotes) after the 2026-10-20 |
| 3 | # T3 Code Nightly update on macOS. See https://github.com/pingdotgg/t3code/issues/22216 |
| 4 | # |
| 5 | # The update moved the encryption key to a new Keychain item, so the app can no |
| 6 | # longer read connection-catalog.json. This script decrypts the file with the |
| 7 | # old key and encrypts it again with the new one. It makes a backup first. |
| 8 | set -euo pipefail |
| 9 | |
| 10 | BIN="/Applications/T3 Code (Nightly).app/Contents/MacOS/T3 Code (Nightly)" |
| 11 | CATALOG="${CATALOG:-$HOME/.t3/userdata/connection-catalog.json}" |
| 12 | NEW_NAME="T3 Code (Nightly)" |
| 13 | OLD_NAMES=("t3code" "t3code-v2" "Electron") |
| 14 | |
| 15 | has_key() { security find-generic-password -s "$1 Safe Storage" -a "$1 Key" >/dev/null 2>&1; } |
| 16 | read_key() { security find-generic-password -s "$1 Safe Storage" -a "$1 Key" -w; } |
| 17 | |
| 18 | [ "$(uname)" = Darwin ] || { echo "This fix is for macOS only."; exit 1; } |
| 19 | [ -x "$BIN" ] || { echo "T3 Code (Nightly) is not in /Applications."; exit 1; } |
| 20 | [ -f "$CATALOG" ] || { echo "No saved connections file at $CATALOG. Nothing to fix."; exit 0; } |
| 21 | if pgrep -xq "T3 Code (Nightly)"; then |
| 22 | echo "Quit T3 Code (Nightly) first (Cmd+Q), then run this again."; exit 1 |
| 23 | fi |
| 24 | has_key "$NEW_NAME" || { echo "Open T3 Code (Nightly) once, quit it, then run this again."; exit 1; } |
| 25 | |
| 26 | # Chromium safeStorage on macOS: "v10" + AES-128-CBC, PBKDF2-SHA1("saltysalt", 1003), IV of spaces. |
| 27 | # Exit codes: 0 = fixed, 2 = already readable, 3 = OLD_KEY is not the right key. |
| 28 | JS=' |
| 29 | const fs = require("fs"), crypto = require("crypto"); |
| 30 | const file = process.env.CATALOG; |
| 31 | const doc = JSON.parse(fs.readFileSync(file, "utf8")); |
| 32 | const blob = Buffer.from(doc.encryptedCatalog, "base64"); |
| 33 | const iv = Buffer.alloc(16, 0x20); |
| 34 | const keyFor = (pw) => crypto.pbkdf2Sync(pw, "saltysalt", 1003, 16, "sha1"); |
| 35 | const decrypt = (pw) => { |
| 36 | if (!pw || blob.subarray(0, 3).toString("latin1") !== "v10") return null; |
| 37 | try { |
| 38 | const d = crypto.createDecipheriv("aes-128-cbc", keyFor(pw), iv); |
| 39 | const text = Buffer.concat([d.update(blob.subarray(3)), d.final()]).toString("utf8"); |
| 40 | return typeof JSON.parse(text) === "object" ? text : null; |
| 41 | } catch { return null; } |
| 42 | }; |
| 43 | if (decrypt(process.env.NEW_KEY) !== null) process.exit(2); |
| 44 | const text = decrypt(process.env.OLD_KEY); |
| 45 | if (text === null) process.exit(3); |
| 46 | const c = crypto.createCipheriv("aes-128-cbc", keyFor(process.env.NEW_KEY), iv); |
| 47 | const out = Buffer.concat([Buffer.from("v10"), c.update(text, "utf8"), c.final()]); |
| 48 | const backup = `${file}.bak-${Date.now()}`; |
| 49 | fs.copyFileSync(file, backup); |
| 50 | const tmp = `${file}.tmp-${process.pid}`; |
| 51 | fs.writeFileSync(tmp, JSON.stringify({ version: 1, encryptedCatalog: out.toString("base64") }) + "\n", { mode: fs.statSync(file).mode }); |
| 52 | fs.renameSync(tmp, file); |
| 53 | console.log(`Backup: ${backup}`); |
| 54 | ' |
| 55 | |
| 56 | run_node() { |
| 57 | CATALOG="$CATALOG" NEW_KEY="$NEW_KEY" OLD_KEY="$1" ELECTRON_RUN_AS_NODE=1 \ |
| 58 | "$BIN" -e "$JS" 2> >(grep -v "Node.js environment variables are disabled" >&2) |
| 59 | } |
| 60 | |
| 61 | echo "macOS will ask for Keychain access. Click Allow (not Always Allow)." |
| 62 | NEW_KEY="$(read_key "$NEW_NAME")" |
| 63 | |
| 64 | rc=0; run_node "" || rc=$? |
| 65 | [ "$rc" = 2 ] && { echo "Your saved connections are already readable. Nothing to fix."; exit 0; } |
| 66 | |
| 67 | for name in "${OLD_NAMES[@]}"; do |
| 68 | has_key "$name" || continue |
| 69 | echo "Trying the \"$name\" key..." |
| 70 | rc=0; run_node "$(read_key "$name")" || rc=$? |
| 71 | if [ "$rc" = 0 ]; then echo "Fixed. Open T3 Code (Nightly) again."; exit 0; fi |
| 72 | [ "$rc" = 3 ] || { echo "Unexpected error (code $rc). Your file was not changed."; exit 1; } |
| 73 | done |
| 74 | |
| 75 | echo "No old key could read your saved connections. Your file was not changed." |
| 76 | echo "Please comment on https://github.com/pingdotgg/t3code/issues/22216" |
| 77 | exit 1 |
Linux is not covered by this script. A fix PR is in progress.
Diagnosed with t3 triage by Claude Opus 5.5 in Claude Code.
I also noticed this. Got scared for a little second, before bunx t3 triage reassured me everything would be fine;)
Thanks @t3dotgg, confirmed that fixes it.
Confirmed the same regression on Linux x64 (Arch, kernel 7.2.11-arch1-1), using the desktop AppImage and its local server.
Reproduction: From an existing Nightly installation with projects and saved environments, I used the update button in the desktop app. The affected server reported 0.1.1-nightly.20261020.3399. After the update, the desktop showed no projects or other environments. Signing in to T3 Connect again did not restore them.
Local evidence (redacted):
desktop.trace.ndjsonrecordedDesktopConnectionCatalogStoreProtectionErrorduringdecrypt-catalogindesktop.connectionCatalogStore.getanddesktop.connectionCatalogStore.recoverPreviousIdentityCatalogat approximately 08:28 and 08:31 UTC on 20 October. The catalog IPC read failed too.- The local server still answered
/.well-known/t3/environmentand reported build.3399before rollback. - The current SQLite database retained 8 non-deleted projects and 9 non-deleted threads. No database repair was needed.
- The
.3399source only tries previous-name recovery forT3 Code (Alpha)and the Beta key; it has no Nightly fallback. This is consistent with the encryption-identity regression diagnosed above. I did not inspect or test the Linux keyring, so the exact key mismatch on this machine is inferred from the code and failure, not independently proven.
Recovery verified: I backed up the existing AppImage and encrypted connection catalog, installed the official .3386 x86_64 AppImage after SHA-256 verification, and restarted the desktop app. The server then reported 0.1.1-nightly.20261020.3386; four post-restart catalog reads succeeded with zero failures, and the user confirmed that projects and environments reappeared. The catalog and database were not edited directly.
This adds a Linux reproduction to the Mac reports. A regression test should cover an existing Linux Nightly catalog across the .3386 → .3399 identity change and verify that local and saved remote environments remain visible after update.
Investigated with t3 triage by OpenAI Codex (GPT-6).
insane response time, fixes it for me too
Same failure here on macOS arm64 (Darwin 27.0), updating Nightly 0.1.1-nightly.20261019.3373 → 0.1.1-nightly.20261020.3399. I can pin down the key mismatch:
Login Keychain before and after (metadata only):
t3code Safe Storage/ accountt3code Key: created 2026-08-16. This is the key the catalog was encrypted with.T3 Code (Nightly) Safe Storage/ accountT3 Code (Nightly) Key: created 2026-10-20 08:13:20Z, the first launch of 3399.
Why: the packaged nightly's app.asar/package.json has "name": "t3code" and no productName. Before #20907, Chromium's OSCrypt therefore initialised under the name t3code. In 3399, DesktopPreReadyPlatform pins resolveDesktopSafeStorageName() = "T3 Code (Nightly)", which creates a fresh key. DesktopSafeStorageIdentity.decryptWithPreviousName returns None unless the pinned name is "T3 Code (Alpha)" (apps/desktop/src/app/DesktopSafeStorageIdentity.ts:29-33). Even then it only tries T3 Code (Beta) Safe Storage, so nightly installs whose catalog sits under t3code Safe Storage get no fallback.
Trace (desktop.trace.ndjson, on every launch of 3399):
| 1 | desktop.safeStorageIdentity.decryptWithPreviousName Success (None) |
| 2 | desktop.connectionCatalogStore.recoverPreviousIdentityCatalog Failure |
| 3 | DesktopConnectionCatalogStoreProtectionError: … decrypt-catalog |
| 4 | ElectronSafeStorageDecryptError → Error while decrypting the ciphertext provided to safeStorage.decryptString. |
| 5 | web.connectionStorage.loadCatalog ConnectionTransientError |
The database was intact (about 1.8k threads); only the catalog was unreadable. Recovery left connection-catalog.json untouched (still written by 3373), so a fixed build can still recover it.
Suggested fix direction: have the fallback try t3code Safe Storage (and probably the old app.getName() value generally) for every channel, not just Alpha → Beta. Stable installs whose packaged name was also t3code may have the same exposure. I haven't verified that.
Workaround that worked: reinstalling 0.1.1-nightly.20261019.3373 from Releases. It decrypts the untouched catalog and accepts the 65/66 migrations with only a divergence warning.
Diagnosed via t3 triage with Claude Opus 5.5 in Claude Code.
The new nightly restored everything for me.