Commit ff06bd5
* fix(storage): token-source duplicate must not disable the real org account on merge (#171)
Closes the last #171 recovery gap, found by testing the REAL persisted path:
when storage dedup collapses a disabled accountIdSource:"token" duplicate (a
plugin re-login artifact) into the matching org-source account by email, the
survivor inherited enabled:false. Combined with codex-doctor/codex-health
skipping disabled accounts, a single-real-account pool could end up dark and
UNRECOVERABLE — the user had no automatic path back.
Root cause: lib/storage/identity.ts mergeAccountRecords had no `enabled`
resolution — the flag fell through the `{...older, ...newest}` spread, and the
disabled token-dup could be the newest record.
Fix: add resolveMergedEnabled(target, source). When exactly one side is
org-like (organizationId present OR accountIdSource==="org") and the other is a
token-source duplicate, the org account's own enabled state governs. Every
other shape stays fail-closed (either disabled => disabled), preserving the
user-disable intent and the same-identity merge semantics from #64/#85.
Added `enabled` to the permissive AccountLike type.
Verified (architect-reviewed): identity.ts is the ONLY home for this
cross-source bug — login-runner's mergeStoredAccountPair only merges
exact-identity collisions (different key namespaces, can't hit the org<->token
shape) and is backstopped by this same fixed dedup on every persist.
Tests:
- test/storage.test.ts: token-dup does not disable org account (fresh token
carried); fail-closed preserved for user-disabled same-identity; two distinct
real org accounts both stay enabled (no #64/#85 regression).
- test/chaos/doctor-recovery-171-stress.test.ts: single-account + disabled dup
recovers end-to-end through real saveAccounts/loadAccounts + recovery helpers.
- Mutation teeth-check: reverting resolveMergedEnabled fails the merge test.
Full suite green (2542 passed, 1 skipped), tsc + eslint + build clean,
npm audit 0 vulnerabilities.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test: route #171 composite managers through liveManagers (PR #180 review)
Greptile P2: the new single-account composite describe created AccountManager
instances directly and disposed them inline, so a mid-test assertion failure
could leak a shutdown listener. Now uses the module-level makeManager() (which
registers in liveManagers) and drains liveManagers in this describe's afterEach,
matching the leak-safe pattern in the rest of the file.
Full suite green (2542 passed, 1 skipped), tsc + eslint clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: neil <neil@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent d14e956 commit ff06bd5
3 files changed
Lines changed: 188 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
23 | 23 | | |
24 | 24 | | |
25 | 25 | | |
| 26 | + | |
26 | 27 | | |
27 | 28 | | |
28 | 29 | | |
| |||
136 | 137 | | |
137 | 138 | | |
138 | 139 | | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
139 | 171 | | |
140 | 172 | | |
141 | 173 | | |
| |||
147 | 179 | | |
148 | 180 | | |
149 | 181 | | |
| 182 | + | |
150 | 183 | | |
151 | 184 | | |
152 | 185 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
28 | 28 | | |
29 | 29 | | |
30 | 30 | | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
31 | 39 | | |
32 | 40 | | |
33 | 41 | | |
| |||
334 | 342 | | |
335 | 343 | | |
336 | 344 | | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
| 391 | + | |
| 392 | + | |
| 393 | + | |
| 394 | + | |
| 395 | + | |
| 396 | + | |
| 397 | + | |
| 398 | + | |
| 399 | + | |
| 400 | + | |
| 401 | + | |
| 402 | + | |
| 403 | + | |
| 404 | + | |
| 405 | + | |
| 406 | + | |
| 407 | + | |
| 408 | + | |
| 409 | + | |
| 410 | + | |
| 411 | + | |
| 412 | + | |
| 413 | + | |
| 414 | + | |
| 415 | + | |
| 416 | + | |
| 417 | + | |
| 418 | + | |
| 419 | + | |
| 420 | + | |
| 421 | + | |
| 422 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
316 | 316 | | |
317 | 317 | | |
318 | 318 | | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
319 | 388 | | |
320 | 389 | | |
321 | 390 | | |
| |||
0 commit comments