Skip to content

fix(channels): preserve credentials on blank config saves#3270

Closed
just-cameron wants to merge 12 commits into
mainfrom
grumble/LET-9461-LET-9268-channel-credential-blanks
Closed

fix(channels): preserve credentials on blank config saves#3270
just-cameron wants to merge 12 commits into
mainfrom
grumble/LET-9461-LET-9268-channel-credential-blanks

Conversation

@just-cameron

@just-cameron just-cameron commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Linear

  • LET-9461
  • LET-9268

Supersedes

Problem

Channel credential hydration and account saves could turn valid credentials or secure-store refs into persisted blanks. Failed, partial, or concurrent mutations could also leave account storage, keyring state, channel metadata, and routing out of sync.

Approach

  • Preserve secure-store refs when hydration fails; never rewrite a missing ref as an empty credential.
  • Scope and serialize hydration per account so one broken account does not block healthy siblings and delayed migration cannot overwrite a newer credential.
  • Treat blank Telegram, Discord, Slack, custom, and schema-declared plugin secret patches as unchanged; non-empty values still replace credentials.
  • Store schema-declared plugin secrets in keyring mode and redact hydrated values and secret placeholders from account/config snapshots.
  • Reject file-mode saves that would serialize an unresolved keyring placeholder or discard its ref.
  • Make user-facing create, update, bind, unbind, start, stop, and delete operations await or safely coordinate credential persistence.
  • Use per-account mutation locks plus expected-current checks so stale async operations conflict instead of overwriting newer metadata or credentials.
  • Compensate partial keyring writes/deletes, account-file failures, route-reset failures, and delete-cleanup failures without restoring stale whole-channel snapshots.
  • Prefer an existing keyring value when plaintext and a persisted ref disagree; plaintext only repairs a ref whose keyring entry is missing.
  • Keep broken accounts visible as redacted snapshots while hydrating healthy siblings independently.
  • Reject enabled Telegram, Discord, and Slack accounts with missing required credentials before adapter creation.

Limits

  • Accounts already persisted with blank credentials still require one credential re-entry.
  • Credential storage and accounts.json cannot form a true atomic transaction across process crashes; keyring-first ordering avoids persisting refs to secrets that were never written, and compensating rollback covers runtime failures.
  • Explicit credential-clear UX and credential-store doctor/status output remain follow-up work.
  • If a plugin manifest is unavailable, existing persisted refs remain authoritative; unknown plaintext fields without schema metadata or refs cannot be inferred as secrets.

Validation

  • Credential storage, persisted-state, transaction, rollback, concurrency, lifecycle, service, websocket account, delete-cleanup, and full websocket protocol test suites pass locally.
  • Deterministic regressions cover concurrent sibling account writes, stale same-account patches, bind-vs-credential updates, hydration-vs-credential updates, partial Slack migration, route rollback, partial keyring failure, and exactly-one delete response.
  • bun run check — 12 checks passed.
  • Commit hooks passed, including Biome, maintainability checks, and tsc --noEmit.
  • The monolithic local unit runner reached 4,896 passing tests but had 9 environment/global-state failures in unrelated Read/tool-context/hook tests. The isolated channel/websocket suite passed; GitHub cross-platform CI is the authoritative full run.

👾 Generated with Letta Code

Fail closed when hydrated channel secret refs cannot be read, and treat blank token fields as unchanged so settings saves cannot wipe existing credentials.

👾 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta Code <noreply@letta.com>
just-cameron and others added 7 commits July 7, 2026 18:03
Add reviewer-requested regression proof around Slack missing-ref hydration, custom secret merges, partial Slack updates, and blank existing-account starts so credential preservation cannot silently regress.

Letta Code (agent-c2adbf5c-8419-4211-8cd8-3740db164974)

👾 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta Code <noreply@letta.com>
Include schema-declared plugin secret fields in account credential storage so plugin config secrets are stripped, hydrated, preserved on missing refs, and deleted with the account.

Letta Code (agent-c2adbf5c-8419-4211-8cd8-3740db164974)

👾 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta Code <noreply@letta.com>
Hydrate and delete existing plugin secret refs even when the current plugin manifest or schema is unavailable, so credential-store cleanup does not depend only on metadata discovery.

Letta Code (agent-c2adbf5c-8419-4211-8cd8-3740db164974)

👾 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta Code <noreply@letta.com>
Consolidate credential validation and secret-key discovery so channel account flows preserve blank-secret behavior without duplicated invariants.

👾 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta Code <noreply@letta.com>
Preserve the split channel architecture while keeping credential refs, scoped hydration, delete cleanup, and file-mode safety intact after the local main merge.

👾 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta Code <noreply@letta.com>
Letta Code (agent-c2adbf5c-8419-4211-8cd8-3740db164974)

Keep channel credential mutations account-scoped and surface keyring failures before user-facing operations report success, preventing failed writes from masquerading as durable configuration.

👾 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta Code <noreply@letta.com>
Letta Code (agent-c2adbf5c-8419-4211-8cd8-3740db164974)

Make keyring-backed account upserts and deletes commit credential changes in the foreground with rollback so storage, cache, and sibling secrets do not diverge on partial failure. Delay live-account auxiliary cleanup until credential/account deletion has succeeded and cover the failure boundaries with regression tests.

👾 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta Code <noreply@letta.com>
just-cameron and others added 4 commits July 13, 2026 17:12
Make account and secret mutations recoverable across live start/delete and route-reset failures so credentials are not corrupted when adjacent cleanup fails.

👾 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta Code <noreply@letta.com>
Letta Code (agent-c2adbf5c-8419-4211-8cd8-3740db164974)

Prevent stale channel credential writes from overwriting newer metadata or secret updates by locking per-account mutation paths and requiring expected-current snapshots.

👾 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta Code <noreply@letta.com>
Letta Code (agent-c2adbf5c-8419-4211-8cd8-3740db164974)

Preserve the credential repair while incorporating the latest channel policy and repository changes.

👾 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta Code <noreply@letta.com>
Letta Code (agent-c2adbf5c-8419-4211-8cd8-3740db164974)

Snapshot target and pairing state before live account deletion cleanup so route, target, pairing, account, and secret state can be restored when target or pairing persistence fails.

👾 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta Code <noreply@letta.com>
@overlord-letta

Copy link
Copy Markdown
Contributor

Closing this broad rewrite. It expanded from the original credential-loss report into mutation serialization, plugin-secret support, and transaction rollback across the channel subsystem, making it too large to review safely. We will replace it with a narrowly scoped fix proven against the original LET-9461 restart/hydration failure.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants