Skip to content

fix: gracefully handle deactivated_workspace — remove only the dead workspace, preserve siblings#85

Merged
ndycode merged 3 commits intondycode:mainfrom
dengerouzzz:main
Mar 19, 2026
Merged

fix: gracefully handle deactivated_workspace — remove only the dead workspace, preserve siblings#85
ndycode merged 3 commits intondycode:mainfrom
dengerouzzz:main

Conversation

@dengerouzzz
Copy link
Copy Markdown
Contributor

@dengerouzzz dengerouzzz commented Mar 19, 2026

Problem

When one workspace in the pool returns 402 deactivated_workspace, the plugin needs to remove only that dead workspace and keep healthy siblings alive.

The first pass fixed the basic failover, but review found three follow-up bugs:

  • workspace identity was too coarse when the same organizationId appeared under different accountId values
  • codex-doctor / codex-health still removed token-invalid entries by a stale refreshToken: key format
  • flagged-account persistence used a non-atomic read-modify-write path that could lose updates under concurrent deactivated-workspace failures

Fix

lib/request/fetch-helpers.ts

  • Keep dedicated isDeactivatedWorkspaceError(errorBody, status) detection for HTTP 402 deactivated workspaces
  • Keep normalized workspace_deactivated error mapping for clearer user-facing handling

index.ts

  • Handle deactivated_workspace before the unsupported-model / rate-limit branches and fail over to the next healthy account/workspace
  • Use one workspace identity consistently across request failover, doctor/health cleanup, and flagged-pool matching
  • Preserve sibling workspaces when organizationId is shared but accountId differs
  • Fix the token-invalid cleanup path so org-scoped accounts are actually removed from active storage
  • Remove flagged entries by workspace identity instead of raw refresh token when deleting from account management

lib/storage.ts

  • Add withFlaggedAccountStorageTransaction(...) so flagged-account updates run under the shared storage lock
  • Normalize flagged-account dedupe by workspace identity instead of raw refreshToken, so sibling workspaces are preserved in flagged storage too

Tests

File Coverage
test/fetch-helpers.test.ts keeps the deactivated-workspace normalization coverage and restores the original indentation around existing blocks
test/index.test.ts covers the status-aware isDeactivatedWorkspaceError mock, deactivated-workspace sibling failover, and deep-check removal of only the dead org-scoped workspace
test/storage.test.ts covers sibling flagged workspaces with shared refresh tokens and serialized flagged-storage read-modify-write updates
test/index-retry.test.ts kept in the targeted run to ensure retry mocks still compile with the helper changes

Validation

  • npm run typecheck
  • npx eslint index.ts lib/storage.ts test/fetch-helpers.test.ts test/index.test.ts test/storage.test.ts
  • npx vitest run test/storage.test.ts test/index.test.ts test/fetch-helpers.test.ts test/index-retry.test.ts

note: greptile review for oc-chatgpt-multi-auth. cite files like lib/foo.ts:123. confirm regression tests + windows concurrency/token redaction coverage.

Greptile Summary

this PR surgically fixes three follow-up bugs from the first deactivated_workspace pass: workspace identity is now keyed by organizationId|accountId so siblings sharing a refreshToken are never collapsed, flagged storage updates go through withFlaggedAccountStorageTransaction to eliminate the read-modify-write race under concurrent deactivated-workspace failures, and removeFromActive now uses the workspace identity key so org-scoped accounts are actually filtered out. the fetch-helpers isDeactivatedWorkspaceError export and the normalizeErrorPayload early-return are clean and well-isolated.

key observations:

  • getWorkspaceIdentityKey in index.ts and getFlaggedIdentityKey in lib/storage.ts implement identical logic independently — if key format drifts between them, active filtering and flagged dedup will silently disagree on which account is which
  • the deactivated_workspace branch in the deep-check probe loop (checkAccountHealth, ~line 3300) has no vitest coverage; the test suite covers request-failover and token-invalid deep-check but not this third path
  • the sibling-failover test asserts vi.mocked(storageModule.saveFlaggedAccounts) rather than withFlaggedAccountStorageTransaction, coupling it to mock wiring rather than the production API
  • on windows, if renameWithWindowsRetry exhausts its retries inside withFlaggedAccountStorageTransaction, the workspace is removed from active storage but never written to flagged storage — a known inconsistency window with no regression test or code comment acknowledging it

Confidence Score: 3/5

  • safe to merge with low risk, but two gaps need follow-up: duplicate identity key logic between index.ts and storage.ts, and missing test coverage for the deep-check deactivated_workspace probe path
  • core logic is sound — transaction wrapper, sibling preservation, and identity key fixes all work correctly. the main risks are the duplicated key derivation code (divergence hazard rather than current bug) and the untested deep-check branch. windows EBUSY exhaustion can produce an active/flagged inconsistency but this is a pre-existing class of failure, not a regression.
  • index.ts (duplicate getWorkspaceIdentityKey, missing deep-check coverage), test/index.test.ts (saveFlaggedAccounts spy assertion)

Important Files Changed

Filename Overview
index.ts adds deactivated_workspace failover before unsupported-model branch, uses withFlaggedAccountStorageTransaction for atomic flagged writes, and fixes removeFromActive to use workspace identity keys — but duplicates getWorkspaceIdentityKey logic from lib/storage.ts and the deep-check deactivated_workspace probe path has no test coverage
lib/storage.ts introduces withFlaggedAccountStorageTransaction under withStorageLock, splits saveFlaggedAccountsUnlocked out of the public API, and fixes flagged dedup to use workspace identity key — renameWithWindowsRetry is used correctly but no test covers the EBUSY-exhaustion inconsistency window for the new transaction path
lib/request/fetch-helpers.ts adds isDeactivatedWorkspaceError export with multi-path code extraction (top-level, detail, error nesting) and normalizes 402 responses to a workspace_deactivated payload — clean, well-scoped, no issues
test/index.test.ts adds sibling failover, workspace-deactivated verify-flagged skip, and deep-check org-scoped removal tests — coverage is good for the request path and verify-flagged path, but the deep-check deactivated_workspace probe branch is missing; test assertion on saveFlaggedAccounts spy is coupled to mock internals rather than the withFlaggedAccountStorageTransaction production API
test/storage.test.ts adds sibling flagged workspace preservation test and a serialized read-modify-write concurrency test for withFlaggedAccountStorageTransaction — well structured and directly targets the new transaction API
test/fetch-helpers.test.ts adds deactivated workspace normalization test; indentation of the describe block shifted to 3 levels deep vs the 2-level pattern of surrounding blocks — no logic issues
test/index-retry.test.ts minimal stub addition: isDeactivatedWorkspaceError: () => false to keep retry mock compile-compatible with the new import — no issues

Sequence Diagram

sequenceDiagram
    participant FH as fetch handler
    participant AM as AccountManager
    participant FW as withFlaggedAccountStorageTransaction
    participant FS as flagged-accounts.json
    participant AS as accounts.json

    FH->>AM: getCurrentOrNextForFamilyHybrid()
    AM-->>FH: account (dead workspace)
    FH->>FH: isDeactivatedWorkspaceError(errorBody, 402)
    Note over FH: workspaceDeactivated = true
    FH->>FW: withFlaggedAccountStorageTransaction(handler)
    FW->>FS: loadFlaggedAccountsUnlocked() [under lock]
    FS-->>FW: current flagged storage
    FW->>FW: upsertFlaggedAccountRecord(nextStorage, flaggedRecord)
    FW->>FS: saveFlaggedAccountsUnlocked(nextStorage) [renameWithWindowsRetry]
    FW-->>FH: done
    FH->>AM: removeAccount(deadWorkspace)
    AM->>AS: saveToDiskDebounced()
    AM-->>FH: removed=true
    FH->>FH: attempted.clear(), break
    Note over FH: retry loop → live sibling workspace
    FH->>AM: getCurrentOrNextForFamilyHybrid()
    AM-->>FH: account (live workspace)
    FH-->>FH: 200 OK
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: index.ts
Line: 188-203

Comment:
**Duplicate identity key logic will silently diverge from `storage.ts`**

`getWorkspaceIdentityKey` in `index.ts` (here) and `getFlaggedIdentityKey` in `lib/storage.ts` (lines 929–946) implement identical key-derivation logic but are completely independent. if one is modified to add a new identity tier (e.g. a `workspaceId` field) without updating the other, accounts added to `removeFromActive` by `index.ts` will never match what `normalizeFlaggedStorage` dedupes — so removed workspaces silently re-appear after storage round-trips.

the `getFlaggedIdentityKey` helper in `storage.ts` should be exported (or extracted to a shared utility) and reused here rather than duplicated. the current two-copy situation is a latent divergence bug waiting for the next refactor.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: index.ts
Line: 3297-3313

Comment:
**no vitest coverage for `deactivated_workspace` in the deep-check probe path**

this branch — triggered when the inner codex quota-probe throws `"deactivated_workspace"` during a `codex-doctor` / `codex-health` run — is not exercised by any of the new tests. the PR adds solid coverage for the request-failover path and the `token-invalid` deep-check path, but the `deactivated_workspace` branch of `checkAccountHealth` is invisible to the test suite.

per the project's 80% coverage threshold (`test/AGENTS.md`) and the pattern set by the "removes only the token-invalid org-scoped workspace" test, a companion test for this branch would complete the picture: mock `queuedRefresh` / the inner codex probe to throw/return `deactivated_workspace`, assert that only that workspace ends up in `flaggedUpdates` and is removed from active, and that siblings with the same `refreshToken` but different `accountId` are preserved.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: test/index.test.ts
Line: 2523-2659

Comment:
**asserting `saveFlaggedAccounts` spy couples the test to mock internals rather than production API**

the assertion `expect(vi.mocked(storageModule.saveFlaggedAccounts)).toHaveBeenCalledTimes(1)` only passes because `withFlaggedAccountStorageTransaction`'s mock wires `persist` to call `mockSaveFlaggedAccounts` — the same function reference as `storageModule.saveFlaggedAccounts`. in production, `withFlaggedAccountStorageTransaction` calls `saveFlaggedAccountsUnlocked`, which is never the exported `saveFlaggedAccounts`. the test works today but will silently mislead on any future refactor of the mock.

a cleaner assertion is:
```typescript
expect(vi.mocked(storageModule.withFlaggedAccountStorageTransaction)).toHaveBeenCalledTimes(1);
```
this directly verifies that the production code chose the transactional API rather than leaking through the mock's plumbing.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: lib/storage.ts
Line: 1116-1126

Comment:
**windows EBUSY exhaustion leaves active pool and flagged pool inconsistent**

`saveFlaggedAccountsUnlocked` uses `renameWithWindowsRetry` (good), but if the rename retries are exhausted (antivirus or another process holding the file), the error propagates out of `withFlaggedAccountStorageTransaction`. the request-failover caller catches this and logs a warning — but by then the workspace has already been removed from active storage via `accountManager.removeAccount`. the result on windows: the dead workspace is gone from active but absent from flagged, so re-add-from-flagged operations can never surface it.

no regression test exists for this windows failure path on the new transaction helper. at minimum, a comment documenting the known inconsistency window and confirming `renameWithWindowsRetry` is the first-line defense would satisfy the project's concurrency-reasoning requirement.

**Rule Used:** What: Every code change must explain how it defend... ([source](https://app.greptile.com/review/custom-context?memory=637a42e6-7a78-40d6-9ef8-6a45e02e73b6))

How can I resolve this? If you propose a fix, please make it concise.

Last reviewed commit: "fix: keep deactivate..."

Context used:

  • Rule used - What: Every code change must explain how it defend... (source)

…d workspace entry

- Add isDeactivatedWorkspaceError() detector in fetch-helpers.ts that
  matches HTTP 402 + detail.code/error.code === 'deactivated_workspace'
- On deactivated_workspace response: flag the specific workspace entry
  (keyed by organizationId > accountId > refreshToken), remove it from
  rotation and failover to the next healthy workspace/account
- Preserve sibling workspaces sharing the same refreshToken — only the
  dead workspace-entry is removed, not the entire user
- Extend codex-health deep probe to flag deactivated workspaces using
  the same identity key (not refreshToken) so multi-workspace users
  aren't penalized for a single bad workspace
- Add test: handleErrorResponse normalizes 402 deactivated_workspace
- Add test: plugin removes only the dead workspace and succeeds on the
  live sibling in the same request
@dengerouzzz dengerouzzz requested a review from ndycode as a code owner March 19, 2026 06:57
@chatgpt-codex-connector
Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 19, 2026

Warning

Rate limit exceeded

@ndycode has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 16 minutes and 23 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1954134c-9821-4f62-ac42-e98d8cbeba43

📥 Commits

Reviewing files that changed from the base of the PR and between e367cc8 and 8f2f93f.

📒 Files selected for processing (3)
  • index.ts
  • lib/storage.ts
  • test/index.test.ts
📝 Walkthrough

Walkthrough

Implements workspace-deactivation detection and identity-keyed flagged-account handling. Deactivated-workspace responses (402 with deactivated_workspace) are detected, trigger token refund/account flagging and removal from rotation via workspace-identity keys, and persist flagged updates transactionally; tests and storage helpers updated accordingly.

Changes

Cohort / File(s) Summary
Request error detection
lib/request/fetch-helpers.ts
Added isDeactivatedWorkspaceError, getStructuredErrorCode, and DEACTIVATED_WORKSPACE_CODE; normalizeErrorPayload short-circuits to return a workspace_deactivated payload for deactivated-workspace errors.
Rotation & deactivation handling
index.ts
Branch request-failure handling to treat deactivated-workspace errors specially: refund token, record failure/metrics, remove account from active rotation or mark cooldown, persist flagged account with flaggedReason: "workspace-deactivated" using workspace-identity helpers (getWorkspaceIdentityKey, matchesWorkspaceIdentity).
Quota probing & deep checks
index.ts
fetchCodexQuotaSnapshot now surfaces deactivated-workspace via Error("deactivated_workspace"); deep health/quota checks accumulate flaggedUpdates by workspace identity and persist removals using transactional flagged storage updates.
Flagged storage transactionality & identity keys
lib/storage.ts
Normalized flagged-storage dedup keys to prefer organizationIdaccountIdrefreshToken; added withFlaggedAccountStorageTransaction and unlocked loader/saver helpers; refactored load/save to run under storage lock.
Flagged-pool persistence & UI deletion
index.ts, ...
Replaced refresh-token-only upserts/deletes with identity-key-based upsert/delete when persisting flagged accounts or removing accounts from pools and UI flows.
Tests — fetch-helpers
test/fetch-helpers.test.ts
Added test asserting isDeactivatedWorkspaceError detects 402/deactivated payload and normalizeErrorPayload yields deactivated_workspace/workspace_deactivated results.
Tests — integration & retry
test/index.test.ts, test/index-retry.test.ts
Expanded mocks to include isDeactivatedWorkspaceError; added tests verifying single workspace removal (not all sharing refresh token), persisted flagged entry with flaggedReason, and deep-check removal behavior.
Tests — flagged storage
test/storage.test.ts
Added tests for identity-key deduplication and concurrent withFlaggedAccountStorageTransaction updates preserving both writes.

Sequence Diagram

sequenceDiagram
    participant Client
    participant RotationManager as Rotation Manager
    participant FetchHelpers as Fetch Helpers
    participant CodexAPI as Codex API
    participant Storage as Flagged Storage

    Client->>RotationManager: sendRequest(req)
    RotationManager->>CodexAPI: forward request with account token
    CodexAPI-->>RotationManager: 402 { detail: { code: "deactivated_workspace" } }

    RotationManager->>FetchHelpers: isDeactivatedWorkspaceError(response)
    FetchHelpers-->>RotationManager: true

    RotationManager->>RotationManager: refund token, record failure, update metrics
    RotationManager->>Storage: withFlaggedAccountStorageTransaction(persist flagged record by workspace identity)
    Storage-->>RotationManager: persisted

    RotationManager->>RotationManager: remove account from active rotation (or mark cooldown)
    RotationManager->>CodexAPI: retry request with next account
    CodexAPI-->>RotationManager: 200 OK

    RotationManager-->>Client: return success response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰🌿 I sniffed the 402, a quiet, falling mute,
Marked one workspace flagged by identity's route.
I hop, I save, transactional and neat,
Swap tokens, retry — onward with fleet feet.
A little rabbit cheer for rotation complete!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: graceful handling of deactivated workspaces while preserving sibling workspaces.
Description check ✅ Passed The pull request description provides comprehensive detail on the problem, fix approach across multiple files, test coverage strategy, and validation steps.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
test/index.test.ts (1)

189-191: Mock only checks one of four error code locations.

The mock implementation only checks error.code, but the real getStructuredErrorCode function (context snippet 1, lines 284-303) extracts codes from four locations in priority order:

  1. Top-level errorBody.code
  2. errorBody.detail.code
  3. errorBody.error.code or errorBody.error.type

This test works because the test data in line 2563 uses { error: { code: "deactivated_workspace" } }, which matches location 3. However, if future tests use different payload shapes (e.g., { detail: { code: "deactivated_workspace" } } as the fetch-helpers test does), this mock would incorrectly return false.

Consider aligning the mock more closely with the real implementation for better test fidelity:

♻️ Suggested mock improvement
-	isDeactivatedWorkspaceError: vi.fn((errorBody: unknown) =>
-		(errorBody as { error?: { code?: string } })?.error?.code === "deactivated_workspace",
-	),
+	isDeactivatedWorkspaceError: vi.fn((errorBody: unknown) => {
+		const body = errorBody as Record<string, unknown> | undefined;
+		if (!body || typeof body !== "object") return false;
+		const code =
+			(typeof body.code === "string" && body.code) ||
+			(typeof (body.detail as Record<string, unknown>)?.code === "string" && (body.detail as Record<string, unknown>).code) ||
+			(typeof (body.error as Record<string, unknown>)?.code === "string" && (body.error as Record<string, unknown>).code);
+		return code === "deactivated_workspace";
+	}),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/index.test.ts` around lines 189 - 191, The test mock is too narrow:
update the isDeactivatedWorkspaceError mock in index.test.ts to mirror
getStructuredErrorCode’s behavior by checking the four locations in priority
(top-level errorBody.code, errorBody.detail.code, then errorBody.error.code, and
errorBody.error.type) and return true if any equals "deactivated_workspace";
reference the mock name isDeactivatedWorkspaceError and the real extractor
getStructuredErrorCode so the mock returns the same boolean for all payload
shapes the real function would handle.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@index.ts`:
- Line 3199: The pruning logic only adds the refresh-token key
(removeFromActive.add(`refreshToken:${account.refreshToken}`)) but later only
filters by the workspace identity (getWorkspaceIdentityKey(account)), so
accounts with workspace metadata stay active; fix by adding the workspace key to
removals as well (call removeFromActive.add(getWorkspaceIdentityKey(account))
wherever you add the refreshToken removal) and update any matching/filtering
logic to consider both keys (i.e., check membership for either
`refreshToken:${account.refreshToken}` or getWorkspaceIdentityKey(account) when
deciding to prune).
- Around line 2395-2411: The code is adding "workspace-deactivated" records into
the generic flagged_accounts pool which verifyFlaggedAccounts() later may
restore; adjust the flow so deactivated workspaces are not placed into the
generic flagged pool or are marked as non-restorable. Specifically, in the block
that builds FlaggedAccountMetadataV1 and calls
loadFlaggedAccounts()/saveFlaggedAccounts(), skip pushing or updating entries
whose flaggedReason === "workspace-deactivated" (or alternatively add a
nonRestorable=true flag to the record) and update verifyFlaggedAccounts() to
ignore records with that marker or reason (referencing loadFlaggedAccounts,
saveFlaggedAccounts, FlaggedAccountMetadataV1, matchesWorkspaceIdentity, and
verifyFlaggedAccounts to locate the related logic).
- Around line 3045-3047: When isDeactivatedWorkspaceError(errorBody,
response.status) is true you must short-circuit the quota-probe loop instead of
letting later errors overwrite it; locate the catch that currently sets
lastError and change the behavior so that upon detecting
isDeactivatedWorkspaceError(...) you immediately rethrow the sentinel
Error("deactivated_workspace") (or set lastError to that sentinel and
break/return from the outer probe loop) so the deactivated-workspace condition
is preserved for the caller; references: isDeactivatedWorkspaceError,
Error("deactivated_workspace"), lastError.

---

Nitpick comments:
In `@test/index.test.ts`:
- Around line 189-191: The test mock is too narrow: update the
isDeactivatedWorkspaceError mock in index.test.ts to mirror
getStructuredErrorCode’s behavior by checking the four locations in priority
(top-level errorBody.code, errorBody.detail.code, then errorBody.error.code, and
errorBody.error.type) and return true if any equals "deactivated_workspace";
reference the mock name isDeactivatedWorkspaceError and the real extractor
getStructuredErrorCode so the mock returns the same boolean for all payload
shapes the real function would handle.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 10a3e0cf-7e92-451e-b7c6-87c39b8b1326

📥 Commits

Reviewing files that changed from the base of the PR and between a19b378 and c56d1ac.

📒 Files selected for processing (5)
  • index.ts
  • lib/request/fetch-helpers.ts
  • test/fetch-helpers.test.ts
  • test/index-retry.test.ts
  • test/index.test.ts

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
lib/storage.ts (1)

924-943: Identity key computation differs intentionally from normal accounts.

The flagged account identity key uses a compound format (organizationId:${org}|accountId:${acc}) when both fields are present, whereas normal accounts (toAccountIdentityKey) use only organizationId:${org}. This difference is intentional—flagged storage needs to track individual workspaces that have been deactivated, rather than collapsing all workspaces under the same organization.

Consider adding a brief inline comment explaining this design decision to prevent future confusion during maintenance.

📝 Suggested documentation
+	// Unlike normal account deduplication which uses only organizationId as the top-level key,
+	// flagged accounts use a compound key to preserve distinct workspace entries that share
+	// an organization but have different accountIds (sibling workspaces).
 	const getFlaggedIdentityKey = (account: {
 		organizationId?: string;
 		accountId?: string;
 		refreshToken: string;
 	}): string => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/storage.ts` around lines 924 - 943, getFlaggedIdentityKey intentionally
uses a compound key (`organizationId:${org}|accountId:${acc}`) when both org and
account are present to track individual flagged workspaces rather than
collapsing them by organization; add a short inline comment inside or
immediately above the getFlaggedIdentityKey function (and/or near
normalizeFlaggedIdentityPart) stating this design decision and why it differs
from toAccountIdentityKey so future maintainers understand the rationale.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@lib/storage.ts`:
- Around line 924-943: getFlaggedIdentityKey intentionally uses a compound key
(`organizationId:${org}|accountId:${acc}`) when both org and account are present
to track individual flagged workspaces rather than collapsing them by
organization; add a short inline comment inside or immediately above the
getFlaggedIdentityKey function (and/or near normalizeFlaggedIdentityPart)
stating this design decision and why it differs from toAccountIdentityKey so
future maintainers understand the rationale.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: fefabee6-49c9-482a-b8f7-b6a522b96b54

📥 Commits

Reviewing files that changed from the base of the PR and between c56d1ac and e367cc8.

📒 Files selected for processing (5)
  • index.ts
  • lib/storage.ts
  • test/fetch-helpers.test.ts
  • test/index.test.ts
  • test/storage.test.ts
✅ Files skipped from review due to trivial changes (1)
  • index.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • test/fetch-helpers.test.ts
  • test/index.test.ts

@ndycode ndycode merged commit 0dc2bb7 into ndycode:main Mar 19, 2026
2 checks passed
ndycode added a commit that referenced this pull request Apr 6, 2026
fix: gracefully handle deactivated_workspace — remove only the dead workspace, preserve siblings
ndycode added a commit that referenced this pull request Apr 6, 2026
fix: gracefully handle deactivated_workspace — remove only the dead workspace, preserve siblings
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