All identities share one keychain item: deleting it destroys managed-agent keys and surfaces as "keyring unreachable"
- Dominant language
- Rust
- Stars
- 32.7k
- Forks
- 4.3k
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 253
Description
## Summary
Desktop stores every identity it manages — the user identity **and** all managed-agent keys — inside a **single** OS keychain item (service `buzz-desktop`), with no export or backup path. Deleting that one item silently destroys all managed-agent keys. The resulting failure is then reported as *"the OS keyring may be unreachable"*, which points the user at the wrong problem: the keyring is fine, the agent's key no longer exists.
## Environment
- Buzz Desktop 0.5.11, macOS 26.4, Apple Silicon
- Hosted community relay, three managed agents (built-in Fizz / Honey / Bumble personas, Codex runtime)
## What happened
To escape the onboarding trap (filed separately), the `buzz-desktop` keychain item had to be deleted to force a fresh first run. Keychain Access showed **exactly one** item matching `buzz`, which reads as "just the user credential". After recovering the user identity from the phone, all three agents refused to start.
## Actual behavior
`Status: Stopped` for every agent, and start is a silent no-op from both entry points (the card's **Start** and the profile panel's **Start agent**):
- no `buzz-acp` process spawns
- `agents/agent-pids/` stays empty
- no new lines in `agents/logs/`
- `last_started_at` in `managed-agents.json` is unchanged, and **`last_error` / `last_error_code` / `last_exit_code` all stay `null`**
- nothing in `log show --predicate 'process == "buzz-desktop"'`
The user-facing message is that the OS keyring may be unavailable/unreachable. The strings in the binary:
```
" has no key in JSON or keyring"
"the OS keyring may be unreachable. Refusing to start without an identity;
retry once the keyring is reachable."
```
The keyring was demonstrably fine — the login keychain was unlocked (`no-timeout`), no `SecurityAgent` prompt was pending, and the app had just written a new credential to it during identity recovery. The real condition was the first string, not the second.
## Why the keys were unrecoverable
`managed-agents.json` holds no secret material — every field was enumerated; there is a `pubkey` and an `auth_tag`, and the `auth_tag` is an owner attestation of the form `["auth", "", "", ""]`, signed *by* the owner. So the agent private keys existed only inside that one deleted keychain item. No local APFS snapshots, no Time Machine destination, and no keychain backup existed, so three agent identities were permanently lost. They survive as archived members on the relay with message history attached, and had to be deleted and will have to be re-minted with new keypairs.
## Expected behavior
1. **Don't conflate "keyring unreachable" with "this agent has no key."** They have different causes and different remedies; report the actual condition. The distinction already exists in the code — it just isn't surfaced.
2. **Record the failure.** A start attempt that fails should write `last_error` / `last_error_code` rather than leaving them `null` and no-op'ing the UI.
3. **Don't put every identity in one opaque, unexportable keychain item.** Either separate items per identity (so a user inspecting the keychain can see what they're about to destroy), or an export/backup path for managed-agent keys equivalent to the one the user identity has via phone recovery.
4. **Consider deriving agent keys from the owner identity**, so that recovering the owner also restores its agents. Today, recovering the user identity fully — same pubkey, `Managed by (you)` shown on every agent — still leaves the agents permanently dead.
## Impact
The user identity has a recovery story (phone pairing, encrypted backup). Managed-agent identities have none, and they are stored in the same single item that a user may reasonably be advised to delete. The lost keys are unrecoverable, and the error message actively directs troubleshooting away from the cause.
Contributor guide
Assessment
This issue has not been assessed yet.