Skip to content

Fix auth keys get 404 and improve Control API error logging#142

Open
mattheworiordan wants to merge 1 commit intomainfrom
fix/api-keys
Open

Fix auth keys get 404 and improve Control API error logging#142
mattheworiordan wants to merge 1 commit intomainfrom
fix/api-keys

Conversation

@mattheworiordan
Copy link
Member

@mattheworiordan mattheworiordan commented Mar 1, 2026

Summary

  • Fix auth keys get returning 404 by using the list endpoint instead of a non-existent single-key endpoint (GET /v1/apps/{appId}/keys/{keyId})
  • Support key lookup by label name (e.g. ably auth keys get Root), key ID, APP_ID.KEY_ID, or full key value
  • Add request method and URL to Control API error logging for easier debugging of failed requests

Note

Medium Risk
Changes key retrieval to always use listKeys + client-side matching, which affects behavior/performance of all commands that call getKey and introduces potential ambiguity when matching by label. Scope is limited to CLI key operations and logging, with tests updated accordingly.

Overview
Fixes auth keys get (and other key flows that rely on ControlApi.getKey) by stopping use of the non-existent single-key GET endpoint and instead fetching /keys and filtering locally.

auth keys get now accepts multiple key identifiers (full key value, APP_ID.KEY_ID, key ID, or key label like Root) and updates help/examples accordingly. Control API error logging is improved to include the request method and url, and unit tests for get/revoke/update are updated to mock the list-keys behavior and new not-found errors.

Written by Cursor Bugbot for commit 054fd5b. This will update automatically on new commits. Configure here.

Summary by CodeRabbit

Release Notes

  • New Features

    • The auth keys get command now supports retrieving keys by label and using a combined app and key identifier, in addition to existing lookup methods.
  • Improvements

    • Enhanced error reporting with additional request context to aid in troubleshooting API failures.

The keys get command hit a non-existent GET /v1/apps/{appId}/keys/{keyId}
endpoint, causing 404 errors. The Control API only supports listing all
keys. Rewrite getKey() to always list keys and filter client-side,
supporting lookup by key ID, APP_ID.KEY_ID, full key value, or label
name (e.g. "Root").

Also add request method and URL to Control API error logging so failed
requests are easier to debug.
@mattheworiordan mattheworiordan requested a review from AndyTWF March 1, 2026 01:46
@vercel
Copy link

vercel bot commented Mar 1, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cli-web-cli Ready Ready Preview, Comment Mar 1, 2026 1:46am

Request Review

@coderabbitai
Copy link

coderabbitai bot commented Mar 1, 2026

Walkthrough

The pull request refactors key retrieval logic to support multiple lookup mechanisms. The getKey method now lists all keys and filters by various identifiers (full key, APP_ID.KEY_ID, key ID, or label) instead of direct ID lookups. Command-level argument parsing was updated to extract appId from the APP_ID.KEY_ID format. Tests were updated with helper utilities to align with the new list-based retrieval approach.

Changes

Cohort / File(s) Summary
Command Logic
src/commands/auth/keys/get.ts
Updated argument parsing to support multiple key identifier formats (APP_ID.KEY_ID, key ID, label, or full value). Added logic to extract appId from APP_ID.KEY_ID format. Updated API call to use keyIdentifier parameter instead of keyId.
Service Layer
src/services/control-api.ts
Refactored getKey method to list all keys and filter by multiple identifier types (full key with colon, APP_ID.KEY_ID format, key ID, or label) instead of direct ID fetch. Enhanced error logging to include HTTP method and URL.
Test Utilities
test/unit/commands/auth/keys/get.test.ts, test/unit/commands/auth/keys/revoke.test.ts, test/unit/commands/auth/keys/update.test.ts
Introduced reusable mock helpers mockKeysList and buildMockKey. Replaced individual key GET endpoint mocks with list-based mocks across all test scenarios. Updated error assertions to reflect list-based lookup behavior. Applied consistent pattern changes across key management command tests.

Sequence Diagram(s)

sequenceDiagram
    participant CLI as CLI Command
    participant Service as getKey Service
    participant API as Backend API
    
    rect rgba(0, 100, 200, 0.5)
    Note over CLI,API: Old Flow (Direct ID Lookup)
    CLI->>Service: getKey(appId, keyId)
    Service->>API: GET /v1/apps/{appId}/keys/{keyId}
    API-->>Service: Key object
    Service-->>CLI: Return Key
    end
    
    rect rgba(0, 150, 100, 0.5)
    Note over CLI,API: New Flow (List & Filter)
    CLI->>Service: getKey(appId, keyIdentifier)
    Service->>API: GET /v1/apps/{appId}/keys
    API-->>Service: List of Keys
    Service->>Service: Filter by identifier<br/>(full key, APP_ID.KEY_ID,<br/>key ID, or label)
    Service-->>CLI: Return matching Key
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 Keys now list before they show,
Filtering through the cached flow,
Multiple formats we embrace,
APP_ID.KEY_ID finds its place!
From direct fetch to search with grace,
One hop closer to a better pace. ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 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 (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the primary changes: fixing a 404 issue in the auth keys get command and improving error logging in the Control API.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/api-keys

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

@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: 1

Caution

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

⚠️ Outside diff range comments (1)
test/unit/commands/auth/keys/revoke.test.ts (1)

46-49: ⚠️ Potential issue | 🔴 Critical

Update tests to use DELETE instead of POST and remove /revoke suffix.

The tests mock POST /v1/apps/${appId}/keys/${mockKeyId}/revoke, but revokeKey in src/services/control-api.ts (line 425) calls this.request<void>(\/apps/${appId}/keys/${keyId}`, "DELETE"), which resolves to DELETE /v1/apps/${appId}/keys/${keyId}`.

The mocked endpoints won't be called by the implementation, breaking test coverage. Change the mocks to use .delete() instead of .post() and remove the /revoke suffix:

  .delete(`/v1/apps/${appId}/keys/${mockKeyId}`)
  .reply(200, {});

This applies to lines 46-49, 68-70, and 85-87.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/unit/commands/auth/keys/revoke.test.ts` around lines 46 - 49, The test
mocks in revoke.test.ts are calling POST
/v1/apps/${appId}/keys/${mockKeyId}/revoke but the implementation revokeKey uses
this.request<void>(`/apps/${appId}/keys/${keyId}`, "DELETE"), so update the nock
mocks to match: replace .post(...) with .delete(...) and remove the trailing
/revoke from the mocked path (i.e., mock DELETE
/v1/apps/${appId}/keys/${mockKeyId}); apply the same change to the other two
mocked blocks referenced around lines 68-70 and 85-87 so the tests exercise
revokeKey correctly.
🧹 Nitpick comments (1)
test/unit/commands/auth/keys/update.test.ts (1)

6-27: Consider extracting shared test helpers to reduce duplication.

The mockKeysList and buildMockKey functions are duplicated across get.test.ts, update.test.ts, and revoke.test.ts. Extracting them to a shared location (e.g., test/helpers/key-mock-helpers.ts) would improve maintainability.

♻️ Example shared helper
// test/helpers/key-mock-helpers.ts
import nock from "nock";

export function mockKeysList(appId: string, keys: Record<string, unknown>[]) {
  return nock("https://control.ably.net")
    .get(`/v1/apps/${appId}/keys`)
    .reply(200, keys);
}

export function buildMockKey(
  appId: string,
  keyId: string,
  overrides: Record<string, unknown> = {},
) {
  return {
    id: keyId,
    appId,
    name: "Test Key",
    key: `${appId}.${keyId}:secret`,
    capability: { "*": ["publish", "subscribe"] },
    created: 1709251200000, // Fixed timestamp for deterministic tests
    modified: 1709251200000,
    ...overrides,
  };
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/unit/commands/auth/keys/update.test.ts` around lines 6 - 27, Extract the
duplicated test helpers into a single module and update the tests to import
them: move mockKeysList and buildMockKey into a new file (e.g.,
test/helpers/key-mock-helpers.ts), export both functions, replace the inline
definitions in get.test.ts, update.test.ts, and revoke.test.ts to import
mockKeysList and buildMockKey, and ensure buildMockKey uses a deterministic
timestamp for created/modified to make tests stable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/commands/auth/keys/get.ts`:
- Around line 43-49: The current parsing of keyIdentifier (the if block using
keyIdentifier.includes("."), parts = keyIdentifier.split("."), and appId = appId
|| parts[0]) can misinterpret labels containing a period (e.g., "v1.0") as
APP_ID.KEY_ID; update the check to only treat the left-side as an appId when it
matches a valid app id pattern: validate parts[0] against an appId
regex/validator (e.g., allowed chars/length or the project’s app id format)
before assigning to appId, and keep the existing behavior of preserving an
explicit appId flag (appId || parts[0]); apply this change in the conditional
that uses keyIdentifier, parts, and appId.

---

Outside diff comments:
In `@test/unit/commands/auth/keys/revoke.test.ts`:
- Around line 46-49: The test mocks in revoke.test.ts are calling POST
/v1/apps/${appId}/keys/${mockKeyId}/revoke but the implementation revokeKey uses
this.request<void>(`/apps/${appId}/keys/${keyId}`, "DELETE"), so update the nock
mocks to match: replace .post(...) with .delete(...) and remove the trailing
/revoke from the mocked path (i.e., mock DELETE
/v1/apps/${appId}/keys/${mockKeyId}); apply the same change to the other two
mocked blocks referenced around lines 68-70 and 85-87 so the tests exercise
revokeKey correctly.

---

Nitpick comments:
In `@test/unit/commands/auth/keys/update.test.ts`:
- Around line 6-27: Extract the duplicated test helpers into a single module and
update the tests to import them: move mockKeysList and buildMockKey into a new
file (e.g., test/helpers/key-mock-helpers.ts), export both functions, replace
the inline definitions in get.test.ts, update.test.ts, and revoke.test.ts to
import mockKeysList and buildMockKey, and ensure buildMockKey uses a
deterministic timestamp for created/modified to make tests stable.

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 040e7df and 054fd5b.

📒 Files selected for processing (5)
  • src/commands/auth/keys/get.ts
  • src/services/control-api.ts
  • test/unit/commands/auth/keys/get.test.ts
  • test/unit/commands/auth/keys/revoke.test.ts
  • test/unit/commands/auth/keys/update.test.ts

Comment on lines +43 to 49
// If keyNameOrValue is in APP_ID.KEY_ID format (one period, no colon), extract appId
if (keyIdentifier.includes(".") && !keyIdentifier.includes(":")) {
const parts = keyIdentifier.split(".");
if (parts.length === 2) {
appId = appId || parts[0];
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential edge case: labels containing a period could be misinterpreted.

If a user has a key with a label like "v1.0" or "Test.Key" and runs ably auth keys get "v1.0" --app APP_ID, the condition on line 44 would match (contains ., no :), and parts.length === 2 would also pass. This would incorrectly attempt to use "v1" as the appId.

Since the user explicitly provides --app, line 47's appId || parts[0] means the explicit flag takes precedence, so this edge case is mitigated when --app is provided. However, without --app, a label like "v1.0" would fail confusingly.

Consider a more robust check, such as validating that the first part looks like a valid app ID format:

🔧 Suggested improvement
     // If keyNameOrValue is in APP_ID.KEY_ID format (one period, no colon), extract appId
     if (keyIdentifier.includes(".") && !keyIdentifier.includes(":")) {
       const parts = keyIdentifier.split(".");
-      if (parts.length === 2) {
+      // Only treat as APP_ID.KEY_ID if exactly 2 parts and first part looks like an app ID
+      // App IDs are typically alphanumeric (e.g., "s57drg")
+      if (parts.length === 2 && /^[a-zA-Z0-9]+$/.test(parts[0])) {
         appId = appId || parts[0];
       }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// If keyNameOrValue is in APP_ID.KEY_ID format (one period, no colon), extract appId
if (keyIdentifier.includes(".") && !keyIdentifier.includes(":")) {
const parts = keyIdentifier.split(".");
if (parts.length === 2) {
appId = appId || parts[0];
}
}
// If keyNameOrValue is in APP_ID.KEY_ID format (one period, no colon), extract appId
if (keyIdentifier.includes(".") && !keyIdentifier.includes(":")) {
const parts = keyIdentifier.split(".");
// Only treat as APP_ID.KEY_ID if exactly 2 parts and first part looks like an app ID
// App IDs are typically alphanumeric (e.g., "s57drg")
if (parts.length === 2 && /^[a-zA-Z0-9]+$/.test(parts[0])) {
appId = appId || parts[0];
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/auth/keys/get.ts` around lines 43 - 49, The current parsing of
keyIdentifier (the if block using keyIdentifier.includes("."), parts =
keyIdentifier.split("."), and appId = appId || parts[0]) can misinterpret labels
containing a period (e.g., "v1.0") as APP_ID.KEY_ID; update the check to only
treat the left-side as an appId when it matches a valid app id pattern: validate
parts[0] against an appId regex/validator (e.g., allowed chars/length or the
project’s app id format) before assigning to appId, and keep the existing
behavior of preserving an explicit appId flag (appId || parts[0]); apply this
change in the conditional that uses keyIdentifier, parts, and appId.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Free Tier Details

Your team is on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle for each member of your team.

To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Key lookup uses wrong app when current app differs
    • Updated auth keys get to always derive appId from an explicit APP_ID.KEY_ID input so lookup targets the correct app even when a different current app is configured.

Create PR

Or push these changes by commenting:

@cursor push 0ed189aa59
Preview (0ed189aa59)
diff --git a/src/commands/auth/keys/get.ts b/src/commands/auth/keys/get.ts
--- a/src/commands/auth/keys/get.ts
+++ b/src/commands/auth/keys/get.ts
@@ -44,7 +44,7 @@
     if (keyIdentifier.includes(".") && !keyIdentifier.includes(":")) {
       const parts = keyIdentifier.split(".");
       if (parts.length === 2) {
-        appId = appId || parts[0];
+        appId = parts[0];
       }
     }
This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.

if (keyIdentifier.includes(".") && !keyIdentifier.includes(":")) {
const parts = keyIdentifier.split(".");
if (parts.length === 2) {
appId = appId || parts[0];
Copy link

Choose a reason for hiding this comment

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

Key lookup uses wrong app when current app differs

High Severity

appId = appId || parts[0] only extracts the APP_ID from a full APP_ID.KEY_ID input when no app is already set. If the user has a current app configured (or passes --app) that differs from the APP_ID in the key name, the command lists keys from the wrong app and fails to find the key. All sibling commands (revoke.ts, update.ts, switch.ts) use appId = parts[0] unconditionally, which is the correct behavior.

Fix in Cursor Fix in Web

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

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant