Skip to content

fix(codex): persist account deletion before cleanup - #1319

Merged
Wibias merged 6 commits into
devfrom
agent/codex-account-delete-atomicity
Aug 8, 2026
Merged

fix(codex): persist account deletion before cleanup#1319
Wibias merged 6 commits into
devfrom
agent/codex-account-delete-atomicity

Conversation

@Wibias

@Wibias Wibias commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Persist Codex account removal in config.json before destructive credential/runtime cleanup.
  • Keep the whole delete sequence under the shared config mutation coordinator so cooperating writers cannot re-add the account between config commit and cleanup.
  • Restore the in-memory config snapshot when config persistence fails, so a failed delete leaves the account, credential, pin/pause/priority state, and runtime health intact.
  • Convert post-commit cleanup failures to a fixed recovery error without reflecting credential-store paths or secret-bearing error details.
  • Keep retained account-selector mappings unchanged so exact routes continue to fail closed and a later re-add can recover the same selector.

Root cause

deleteCodexAccount() tombstoned the credential and purged runtime state before the caller persisted the config mutation. If saveConfigPreservingClaudeCode() then failed, the durable config could still contain the account while its credential was already tombstoned and its live state had been cleared.

Tests

Added tests/codex-account-delete-atomicity.test.ts covering:

  • config persistence failure leaves the configured account and destructive state intact;
  • successful deletion commits config before credential/runtime cleanup;
  • cleanup failure keeps the config deletion durable, exposes only a fixed privacy-safe recovery error, and remains retry-safe.

Notes

This is the separate non-blocking hardening follow-up identified while reviewing #1303. It is intentionally scoped to account-delete persistence ordering and does not change the account-picker feature itself.

CI should run the repository validation for this branch; no local checkout was available in this session to run Bun tests before publishing.

Summary by CodeRabbit

  • Bug Fixes

    • Improved account deletion reliability by saving configuration changes before removing credentials and runtime data.
    • Failed configuration saves now preserve the account and its associated state.
    • Cleanup failures now provide a clear recovery error while keeping the deletion safely recorded.
    • Account deletion cleanup can be retried safely after an interrupted operation.
  • Tests

    • Added coverage for deletion ordering, rollback behavior, failure handling, and retry-safe cleanup.

@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 151374b9-1271-4b91-98fe-5daa37e064e8

📥 Commits

Reviewing files that changed from the base of the PR and between 7f57797 and 528dcb7.

📒 Files selected for processing (1)
  • tests/codex-account-delete-atomicity.test.ts

📝 Walkthrough

Walkthrough

Account deletion now commits configuration changes before credential and runtime cleanup. Persistence failures restore runtime and persisted configuration. Post-commit cleanup failures raise dedicated errors. Tests cover rollback, ordering, sanitized errors, and retries.

Changes

Account deletion atomicity

Layer / File(s) Summary
Deletion persistence and cleanup
src/codex/account-lifecycle.ts
The deletion flow uses a mutation lock, snapshots configuration, persists account removal before cleanup, restores configuration after persistence failures, and reports cleanup failures with dedicated errors.
Deletion atomicity validation
tests/codex-account-delete-atomicity.test.ts
Tests verify rollback, preservation of destructive state after failures, durable deletion ordering, sanitized cleanup errors, and retry-safe cleanup.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant deleteCodexAccount
  participant ConfigPersistence
  participant CredentialRuntimeCleanup

  deleteCodexAccount->>ConfigPersistence: Persist account removal
  alt Persistence fails
    ConfigPersistence-->>deleteCodexAccount: Return failure
    deleteCodexAccount->>ConfigPersistence: Restore configuration snapshots
  else Persistence succeeds
    ConfigPersistence-->>deleteCodexAccount: Confirm durable deletion
    deleteCodexAccount->>CredentialRuntimeCleanup: Remove credentials and runtime state
    CredentialRuntimeCleanup-->>deleteCodexAccount: Return cleanup result
  end
Loading

Possibly related PRs

Suggested reviewers: ingwannu, lidge-jun

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: persisting Codex account deletion before credential and runtime cleanup.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch agent/codex-account-delete-atomicity

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the bug Something isn't working label Aug 8, 2026
@Wibias
Wibias marked this pull request as ready for review August 8, 2026 20:59

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/codex/account-lifecycle.ts`:
- Around line 115-121: The save failure path around
saveConfigPreservingClaudeCode must distinguish failures before and after
durable persistence. Update the contract or rollback handling so config.json is
restored to its prior file state when post-write steps such as
bumpGenerationForCooperatingConfigWrite or adoptCustomModelCatalogMigration
fail, and only call restoreRuntimeConfig(runtimeConfig, previousConfig) when
persistence never completed.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 099b82a8-f078-40be-853d-ced85a2f8abf

📥 Commits

Reviewing files that changed from the base of the PR and between de6f3db and dae97e4.

📒 Files selected for processing (2)
  • src/codex/account-lifecycle.ts
  • tests/codex-account-delete-atomicity.test.ts

Comment thread src/codex/account-lifecycle.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/codex/account-lifecycle.ts (2)

139-143: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Preserve picker visibility when credential cleanup fails.

src/codex/account-lifecycle.ts:99-143 computes pickerVisibilityChanged from the pre-delete account row, then src/codex/account-lifecycle.ts:142 throws CodexAccountDeleteCleanupError. If a caller retries through src/codex/auth-api.ts:1399-1416, the first attempt has already removed the account from runtimeConfig.codexAccounts, so the retry returns false and /api/sync receives catalogRefreshPending: false for a row that only disappeared from the picker selection map.

Carry the visibility-change flag on the cleanup error, have the cleanup path converge the deleted binding before throwing, or make the retry loop call convergeAccountNamespaceCatalog with the same flag.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/codex/account-lifecycle.ts` around lines 139 - 143, Preserve the computed
picker visibility state when account cleanup fails: update the cleanup error
flow around pickerVisibilityChanged and CodexAccountDeleteCleanupError so the
deleted binding is converged before throwing, or the same flag is propagated and
reused by the retry loop in the auth API. Ensure retries through the account
deletion path retain the original visibility-change result for
catalogRefreshPending.

140-143: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Handle post-callback COMMIT failures in deleteCodexAccount.

withConfigMutationLockSync commits after deleteCodexAccount’s callback returns, and saveConfigPreservingClaudeCode already persists config.json inside that callback. If the outer database.exec("COMMIT") then fails, deleteCodexAccount exits before any rollback, while codex-accounts.json and generation/state can still be partially updated. Define an idempotent delete/retry recovery path for post-callback commit failures or constrain the mutation contract to make this state impossible.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/codex/account-lifecycle.ts` around lines 140 - 143, Update
deleteCodexAccount and its withConfigMutationLockSync mutation flow to recover
when the post-callback database COMMIT fails after
saveConfigPreservingClaudeCode has persisted changes. Add an idempotent
delete/retry recovery path that restores or completes codex-accounts.json and
generation/state consistently, and ensure cleanupFailed handling remains correct
across retries.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/codex/account-lifecycle.ts`:
- Around line 118-126: Update the hasPersistedConfig error path around
saveConfigPreservingClaudeCode so it tracks whether the durable file replacement
completed, compensates by restoring the previous config when it did, and
restores runtimeConfig before rethrowing. Ensure runtimeConfig is restored on
every failure, while the durable config is restored only after a completed
write, and expose the completion status through the persistence API as needed.
- Around line 91-95: Update the documentation near the account lifecycle
sequence to state that transient callers with hasPersistedConfig false skip
durable config persistence, while still mutating runtimeConfig and performing
credential removal, runtime-state purge, and websocket invalidation. Keep the
existing description of persisted-config ordering and coordination intact.

---

Outside diff comments:
In `@src/codex/account-lifecycle.ts`:
- Around line 139-143: Preserve the computed picker visibility state when
account cleanup fails: update the cleanup error flow around
pickerVisibilityChanged and CodexAccountDeleteCleanupError so the deleted
binding is converged before throwing, or the same flag is propagated and reused
by the retry loop in the auth API. Ensure retries through the account deletion
path retain the original visibility-change result for catalogRefreshPending.
- Around line 140-143: Update deleteCodexAccount and its
withConfigMutationLockSync mutation flow to recover when the post-callback
database COMMIT fails after saveConfigPreservingClaudeCode has persisted
changes. Add an idempotent delete/retry recovery path that restores or completes
codex-accounts.json and generation/state consistently, and ensure cleanupFailed
handling remains correct across retries.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 802246b5-6662-45b8-bcc6-a915eeb2a3bc

📥 Commits

Reviewing files that changed from the base of the PR and between dae97e4 and a8a76f0.

📒 Files selected for processing (1)
  • src/codex/account-lifecycle.ts

Comment thread src/codex/account-lifecycle.ts Outdated
Comment thread src/codex/account-lifecycle.ts Outdated
Comment on lines +118 to +126
if (hasPersistedConfig) {
try {
// Persist first for durable configs. Destructive cleanup below must never run for a
// deletion that failed to commit. Transient configs intentionally skip this write.
saveConfigPreservingClaudeCode(runtimeConfig);
} catch (error) {
restoreRuntimeConfig(runtimeConfig, previousConfig);
throw error;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Restore durable config state when persistence fails after the file write.

saveConfigPreservingClaudeCode can complete persistConfigUnlocked and then throw in bumpGenerationForCooperatingConfigWrite() or adoptCustomModelCatalogMigration(). The catch at Line [124] restores only runtimeConfig; it does not restore config.json. The caller can receive a persistence error while the account is already durably removed, leaving credentials without the corresponding account row.

Make the persistence API report whether the file replacement completed, or perform a compensating durable restore before rethrowing. Restore runtimeConfig only when the durable write did not complete. This is the same unresolved durability gap identified in the previous review.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/codex/account-lifecycle.ts` around lines 118 - 126, Update the
hasPersistedConfig error path around saveConfigPreservingClaudeCode so it tracks
whether the durable file replacement completed, compensates by restoring the
previous config when it did, and restores runtimeConfig before rethrowing.
Ensure runtimeConfig is restored on every failure, while the durable config is
restored only after a completed write, and expose the completion status through
the persistence API as needed.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/codex-account-delete-atomicity.test.ts`:
- Around line 87-104: Update the test around deleteCodexAccount to capture the
original config.json bytes before invoking the forced post-write failure, then
read the file after rollback and assert the bytes match exactly. Keep the
existing in-memory and account-state assertions, but add the exact
persisted-config restoration check so fields such as pins, pauses, priorities,
and selector mappings are covered.
🪄 Autofix

❌ Autofix failed (check again to retry)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 834269c8-3ace-42da-9097-bdb52d756730

📥 Commits

Reviewing files that changed from the base of the PR and between a8a76f0 and 7f57797.

📒 Files selected for processing (2)
  • src/codex/account-lifecycle.ts
  • tests/codex-account-delete-atomicity.test.ts

Comment thread tests/codex-account-delete-atomicity.test.ts
@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Autofix skipped. No unresolved CodeRabbit review comments with fix instructions found.

@Wibias
Wibias merged commit e61f63f into dev Aug 8, 2026
35 of 38 checks passed
@Wibias
Wibias deleted the agent/codex-account-delete-atomicity branch August 8, 2026 21:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant