Skip to content

fix(impersonate): revoke the live session when the actor token was already accepted#382

Merged
rafa-thayto merged 2 commits into
mainfrom
rafa-thayto/imp-revoke-fix
Jul 14, 2026
Merged

fix(impersonate): revoke the live session when the actor token was already accepted#382
rafa-thayto merged 2 commits into
mainfrom
rafa-thayto/imp-revoke-fix

Conversation

@rafa-thayto

@rafa-thayto rafa-thayto commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Summary

clerk imp revoke act_... failed with (400): cannot revoke once the impersonation sign-in URL had been opened: BAPI's POST /v1/actor_tokens/{id}/revoke only revokes pending tokens, and accepting the ticket flips the token to accepted while the live impersonation persists as a session. There was no way to end that session from the CLI.

  • clerk imp revoke now detects the 400 and falls back to session revocation: GET /v1/sessions?user_id=<user>&status=active, filtered to sessions whose actor.sub matches the operator's cli:<email> stamp (prefix match, so --actor +<context> variants also match), then POST /v1/sessions/{id}/revoke for each — sessions started by other operators are never touched.
  • New --user <user_id> option on clerk imp revoke (required for the fallback — BAPI has no GET /v1/actor_tokens/{id}, so the target user can't be recovered from the token ID). Without it, the fallback errors with the new actor_token_already_accepted code and shows how to re-run; no matching session errors with impersonation_session_not_found.
  • The Revoke with: hint printed at creation now includes --user <user_id> so the copied command works in both the pending and accepted states.
  • Agent mode emits { "id": "<act_id>", "status": "accepted", "revokedSessionIds": ["sess_..."] } when the fallback runs, and never prompts.
  • New lib/sessions.ts BAPI client (tolerates both plain-array and paginated { data } list shapes), isActorTokenNotRevocableError helper in lib/actor-tokens.ts, README + changeset (minor) updates.

Test plan

  • 8 new unit tests in revoke.test.ts (TDD, watched fail first): fallback with/without --user, actor-stamp filtering incl. +<context> and foreign/plain sessions, agent-mode JSON payload, no-match error, paginated list shape, non-400 passthrough, --user format validation
  • impersonate.test.ts asserts the creation hint includes --user <user_id>
  • bun run format:check, bun run lint, bun run typecheck, bun run test (1904 pass) all green
  • bun run test:e2e:op — could not run from this environment (1Password vault unavailable); CI runs test:e2e

@changeset-bot

changeset-bot Bot commented Jul 14, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: b6363a4

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
clerk Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Impersonation revocation now accepts an optional --user argument. It first revokes pending actor tokens; when the token has already been accepted, it lists active sessions for the target user, matches sessions by actor stamp, and revokes the matching sessions. New error codes, session API helpers, CLI examples, documentation, output hints, and tests cover the fallback behavior, response formats, validation, and error handling.

Estimated code review effort: 3 (Moderate) | ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.18% 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
Title check ✅ Passed The title clearly states the main change: revoking the live session after an accepted actor token.
Description check ✅ Passed The description matches the implemented fallback, CLI option, errors, tests, and docs.
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.

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
packages/cli-core/src/commands/impersonate/README.md (1)

65-70: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add a language to the fenced transcript block.

markdownlint flags this fence for missing a language identifier (MD040).

🔧 Suggested fix
-```
+```sh
 $ clerk imp revoke act_29w9... --user user_2x9k
 ▲ Token already accepted — an active impersonation session exists.
 ◇ Found session sess_9k2j... (actor: cli:[email protected])
 ✔ Revoked session sess_9k2j... — impersonation ended.
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

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

In @packages/cli-core/src/commands/impersonate/README.md around lines 65 - 70,
Update the fenced transcript block in the impersonation README to specify the
shell language identifier sh, while preserving all command output unchanged.


</details>

<!-- cr-comment:v1:25336be72a8ccb1b93388119 -->

_Source: Linters/SAST tools_

</blockquote></details>
<details>
<summary>packages/cli-core/src/commands/impersonate/revoke.ts (1)</summary><blockquote>

`107-116`: _🩺 Stability & Availability_ | _🔵 Trivial_ | _⚡ Quick win_

**Partial revoke progress is lost if a mid-loop revoke fails.**

If session N of M fails to revoke, `revokedSessionIds` collected so far (and any human-mode success logs already printed) are discarded — the thrown error gives no indication that earlier sessions in this batch were already revoked. A retry is safe (the "active" status filter naturally excludes already-revoked sessions), but the immediate failure is misleading about what state was left behind.

Consider catching per-session failures to report which sessions were revoked before continuing to throw.




<details>
<summary>♻️ Suggested fix: report partial progress before rethrowing</summary>

```diff
   const revokedSessionIds: string[] = [];
   for (const session of matches) {
     if (isHuman()) log.info(`Found session ${session.id} (actor: ${session.actor?.sub})`);
-    await withApiContext(
-      revokeSession(secretKey, session.id),
-      `Failed to revoke session ${session.id}`,
-    );
-    revokedSessionIds.push(session.id);
-    if (isHuman()) log.success(`Revoked session ${session.id} — impersonation ended.`);
+    try {
+      await withApiContext(
+        revokeSession(secretKey, session.id),
+        `Failed to revoke session ${session.id}`,
+      );
+    } catch (error) {
+      if (revokedSessionIds.length > 0 && isHuman()) {
+        log.warn(`Already revoked before this failure: ${revokedSessionIds.join(", ")}`);
+      }
+      throw error;
+    }
+    revokedSessionIds.push(session.id);
+    if (isHuman()) log.success(`Revoked session ${session.id} — impersonation ended.`);
   }
🤖 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 `@packages/cli-core/src/commands/impersonate/revoke.ts` around lines 107 - 116,
Update the revoke loop around revokedSessionIds and withApiContext so a
per-session failure reports the session IDs successfully revoked earlier in the
batch before rethrowing the original error. Preserve the existing success
logging and continue propagating failures, while making the partial-progress
report available when any mid-loop revoke fails.
🤖 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 `@packages/cli-core/src/lib/sessions.ts`:
- Around line 31-54: Update listUserSessions to request all relevant sessions by
adding the API’s supported pagination parameter with a limit of 500 to the
URLSearchParams before calling bapiRequest. Preserve the existing user_id/status
filters and response-shape handling so revokeImpersonationSessions can find
later active sessions.

---

Nitpick comments:
In `@packages/cli-core/src/commands/impersonate/README.md`:
- Around line 65-70: Update the fenced transcript block in the impersonation
README to specify the shell language identifier `sh`, while preserving all
command output unchanged.

In `@packages/cli-core/src/commands/impersonate/revoke.ts`:
- Around line 107-116: Update the revoke loop around revokedSessionIds and
withApiContext so a per-session failure reports the session IDs successfully
revoked earlier in the batch before rethrowing the original error. Preserve the
existing success logging and continue propagating failures, while making the
partial-progress report available when any mid-loop revoke fails.
🪄 Autofix (Beta)

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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1800be1f-f424-49a3-a786-cf1c56fcbf42

📥 Commits

Reviewing files that changed from the base of the PR and between 8bd481d and e276573.

📒 Files selected for processing (10)
  • .changeset/imp-revoke-fix.md
  • packages/cli-core/src/commands/impersonate/README.md
  • packages/cli-core/src/commands/impersonate/impersonate.test.ts
  • packages/cli-core/src/commands/impersonate/impersonate.ts
  • packages/cli-core/src/commands/impersonate/index.ts
  • packages/cli-core/src/commands/impersonate/revoke.test.ts
  • packages/cli-core/src/commands/impersonate/revoke.ts
  • packages/cli-core/src/lib/actor-tokens.ts
  • packages/cli-core/src/lib/errors.ts
  • packages/cli-core/src/lib/sessions.ts
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • clerk/clerk_go (manual)
  • clerk/dashboard (manual)
  • clerk/accounts (manual)
  • clerk/backoffice (manual)
  • clerk/clerk (manual)
  • clerk/clerk-docs (manual)
  • clerk/cloudflare-workers (manual)

Comment thread packages/cli-core/src/lib/sessions.ts
…ready accepted

BAPI's POST /v1/actor_tokens/{id}/revoke only revokes pending tokens —
opening the sign-in URL consumes the ticket, flips the token to accepted,
and the revoke answers 400 forever after, while the impersonation lives on
as a session. `clerk imp revoke` now falls back on that 400: with a new
--user <user_id> option it lists the user's active sessions, keeps only
those stamped with the operator's cli:<email> actor (prefix match so
+<context> variants count), and revokes each one. Without --user it errors
with actor_token_already_accepted and shows how to re-run. The creation
hint printed by `clerk imp` now includes --user so the copied command
works in both the pending and accepted states. Agent mode emits
{ id, status: "accepted", revokedSessionIds } when the fallback runs.
@rafa-thayto rafa-thayto force-pushed the rafa-thayto/imp-revoke-fix branch from d16bfc6 to ea3e1a9 Compare July 14, 2026 16:09

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
packages/cli-core/src/commands/impersonate/README.md (1)

65-70: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add a language to the transcript fence (MD040).

Static analysis flags this fenced block as missing a language identifier.

📝 Proposed fix
-```
+```text
 $ clerk imp revoke act_29w9... --user user_2x9k
 ▲ Token already accepted — an active impersonation session exists.
 ◇ Found session sess_9k2j... (actor: cli:[email protected])
 ✔ Revoked session sess_9k2j... — impersonation ended.
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

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

In @packages/cli-core/src/commands/impersonate/README.md around lines 65 - 70,
Update the transcript code fence in the impersonation README example to include
the text language identifier, preserving the command output contents unchanged.


</details>

<!-- cr-comment:v1:71ce6db23071f3dea1555ab1 -->

_Source: Linters/SAST tools_

</blockquote></details>

</blockquote></details>

<details>
<summary>🤖 Prompt for all review comments with AI agents</summary>

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

Nitpick comments:
In @packages/cli-core/src/commands/impersonate/README.md:

  • Around line 65-70: Update the transcript code fence in the impersonation
    README example to include the text language identifier, preserving the command
    output contents unchanged.

</details>

---

<details>
<summary>ℹ️ Review info</summary>

<details>
<summary>⚙️ Run configuration</summary>

**Configuration used**: Organization UI

**Review profile**: CHILL

**Plan**: Pro Plus

**Run ID**: `838d0ecd-a0a5-4ff3-9b62-c4c21f5e90ce`

</details>

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between d16bfc6293c5c6e4811338ddaeb415ca739450ed and ea3e1a9ac59324ddc3662e1fc079cca228320b74.

</details>

<details>
<summary>📒 Files selected for processing (10)</summary>

* `.changeset/imp-revoke-fix.md`
* `packages/cli-core/src/commands/impersonate/README.md`
* `packages/cli-core/src/commands/impersonate/impersonate.test.ts`
* `packages/cli-core/src/commands/impersonate/impersonate.ts`
* `packages/cli-core/src/commands/impersonate/index.ts`
* `packages/cli-core/src/commands/impersonate/revoke.test.ts`
* `packages/cli-core/src/commands/impersonate/revoke.ts`
* `packages/cli-core/src/lib/actor-tokens.ts`
* `packages/cli-core/src/lib/errors.ts`
* `packages/cli-core/src/lib/sessions.ts`

</details>

<details>
<summary>🔗 Linked repositories identified</summary>

CodeRabbit considers these linked repositories for cross-repo context during reviews:

- `clerk/clerk_go` _(manual)_
- `clerk/dashboard` _(manual)_
- `clerk/accounts` _(manual)_
- `clerk/backoffice` _(manual)_
- `clerk/clerk` _(manual)_
- `clerk/clerk-docs` _(manual)_
- `clerk/cloudflare-workers` _(manual)_

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (5)</summary>

* packages/cli-core/src/commands/impersonate/index.ts
* packages/cli-core/src/lib/errors.ts
* packages/cli-core/src/lib/sessions.ts
* packages/cli-core/src/commands/impersonate/impersonate.ts
* packages/cli-core/src/commands/impersonate/revoke.ts

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

@rafa-thayto rafa-thayto requested a review from wyattjoh July 14, 2026 16:20
Comment thread .changeset/imp-revoke-fix.md
Comment thread packages/cli-core/src/lib/sessions.ts
GET /v1/sessions is unpaginated and always returns a plain array, same
as /v1/users — the `{ data: [...] }` envelope branch was dead code.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
packages/cli-core/src/lib/sessions.ts (1)

31-50: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Default limit (10) on GET /v1/sessions can still cause missed matches.

The prior review flagged that GET /v1/sessions defaults to a 10-item page, so listUserSessions could silently miss later active sessions and cause the caller (revokeImpersonationSessions) to wrongly report impersonation_session_not_found. The maintainer's reply ("This list endpoint only exists in a single state, let's simplify this here") addressed the response-shape normalization (dropping the paginated-envelope branch, per the commit message), but the request itself still only sends user_id/status (Lines 35-38) — no limit or paginated param was added. For users with many sessions, matching sessions beyond the first page will still be missed.

🐛 Proposed fix to request a larger page
   const params = new URLSearchParams({ user_id: query.userId });
   if (query.status) {
     params.set("status", query.status);
   }
+  params.set("limit", "500");
🤖 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 `@packages/cli-core/src/lib/sessions.ts` around lines 31 - 50, Update
listUserSessions to request all relevant sessions by adding the API’s supported
unpaginated or sufficiently large limit parameter to the URLSearchParams
alongside user_id and status. Preserve the existing array response normalization
and ensure revokeImpersonationSessions can find matches beyond the default
10-item page.
🤖 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.

Duplicate comments:
In `@packages/cli-core/src/lib/sessions.ts`:
- Around line 31-50: Update listUserSessions to request all relevant sessions by
adding the API’s supported unpaginated or sufficiently large limit parameter to
the URLSearchParams alongside user_id and status. Preserve the existing array
response normalization and ensure revokeImpersonationSessions can find matches
beyond the default 10-item page.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 27d853c6-160f-41ee-a55b-24f1ab6ca4d3

📥 Commits

Reviewing files that changed from the base of the PR and between ea3e1a9 and b6363a4.

📒 Files selected for processing (2)
  • packages/cli-core/src/commands/impersonate/revoke.test.ts
  • packages/cli-core/src/lib/sessions.ts
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • clerk/clerk_go (manual)
  • clerk/dashboard (manual)
  • clerk/accounts (manual)
  • clerk/backoffice (manual)
  • clerk/clerk (manual)
  • clerk/clerk-docs (manual)
  • clerk/cloudflare-workers (manual)
💤 Files with no reviewable changes (1)
  • packages/cli-core/src/commands/impersonate/revoke.test.ts

@rafa-thayto rafa-thayto requested a review from wyattjoh July 14, 2026 17:49
@rafa-thayto rafa-thayto merged commit f86b4b4 into main Jul 14, 2026
10 checks passed
@rafa-thayto rafa-thayto deleted the rafa-thayto/imp-revoke-fix branch July 14, 2026 19:32
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