Skip to content

[DX-255] Improve error code formatting#174

Draft
umair-ably wants to merge 1 commit intomainfrom
DX-255-error-codes
Draft

[DX-255] Improve error code formatting#174
umair-ably wants to merge 1 commit intomainfrom
DX-255-error-codes

Conversation

@umair-ably
Copy link
Contributor

@umair-ably umair-ably commented Mar 13, 2026

Summary by CodeRabbit

Bug Fixes

  • Error messages now include helpful hints explaining common Ably SDK errors to better assist users with troubleshooting issues.
  • Capability and permission-related errors now provide clearer guidance with helpful references to Ably dashboard settings and configurations.
  • Enhanced error handling across all subscription commands for more robust operation and significantly improved user feedback when failures occur.

@umair-ably umair-ably requested a review from Copilot March 13, 2026 02:43
@vercel
Copy link

vercel bot commented Mar 13, 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 13, 2026 10:45am

Request Review

@coderabbitai
Copy link

coderabbitai bot commented Mar 13, 2026

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 18d1a8b0-1681-4ff8-b5f9-3f0be9accfc4

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

The pull request converts synchronous subscription calls to awaited asynchronous operations across multiple CLI commands, introduces a helper function for friendly Ably error hints, redirects SDK error logs to stderr, and appends contextual error messages to improve user experience with error information.

Changes

Cohort / File(s) Summary
Error Handling & Utilities
src/base-command.ts, src/utils/errors.ts
Added getFriendlyAblyErrorHint() function to provide user-friendly error context. Modified base command to redirect Ably SDK logs to stderr and append friendly hints to error messages in the fail() method.
Subscription Awaiting - Core Channels
src/commands/channels/occupancy/subscribe.ts, src/commands/channels/presence/enter.ts, src/commands/channels/subscribe.ts
Converted channel.subscribe and channel.presence.subscribe calls from synchronous to awaited asynchronous operations, ensuring subscription setup completes before subsequent code execution.
Subscription Awaiting - Presence
src/commands/channels/presence/subscribe.ts
Refactored presence subscription to awaited setup; introduced presenceData object with precomputed timestamp; reordered logging logic to consolidate output handling within subscription callback; adjusted timestamp defaulting behavior.
Subscription Awaiting - Logs
src/commands/logs/channel-lifecycle/subscribe.ts, src/commands/logs/connection-lifecycle/subscribe.ts, src/commands/logs/push/subscribe.ts, src/commands/logs/subscribe.ts
Converted all channel.subscribe calls to awaited operations. In logs/subscribe.ts, refactored to collect subscription promises and await all via Promise.all before proceeding.
Error Handling Tests
test/unit/commands/channels/occupancy/subscribe.test.ts, test/unit/commands/channels/presence/subscribe.test.ts, test/unit/commands/channels/subscribe.test.ts
Added test cases verifying graceful handling of capability-denied errors, asserting error messages include references to channel denial, capability, and Ably dashboard.
Utilities Tests
test/unit/utils/errors.test.ts
Comprehensive test suite for error utilities covering errorMessage() and getFriendlyAblyErrorHint() with specific Ably error codes and edge cases.

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 We hop through subscriptions with await so true,
No more fire-and-forget, just promises anew!
Error hints bloom like carrots, all friendly and kind,
Stderr whispers warnings, peace of mind we will find!
Thump, thump! goes the logic, now perfectly timed. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The PR title 'Improve error code formatting' is partially related to the changeset but does not accurately represent the main changes. The PR primarily adds async/await patterns to subscription calls across multiple files and introduces a friendly error hint helper function, rather than focusing on error code formatting. Consider a more descriptive title that captures the core changes, such as 'Make channel subscriptions async and add friendly error hints' or similar, to better reflect the substantial refactoring of subscription patterns and error handling improvements.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch DX-255-error-codes
📝 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.

@umair-ably
Copy link
Contributor Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Mar 13, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces friendlier, actionable messaging for known Ably error codes (notably capability/auth failures) and adds unit coverage to verify the updated error output across subscribe-style commands.

Changes:

  • Added getFriendlyAblyErrorHint() and appended its hint output to fail() formatting.
  • Updated multiple subscribe commands to await subscription setup and added new unit tests for capability errors.
  • Adjusted SDK error logging behavior in non-verbose mode to avoid polluting stdout.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
src/utils/errors.ts Adds Ably error-code → friendly-hint lookup helper.
src/base-command.ts Appends friendly hint to human-readable errors; tweaks SDK error logging destination.
src/commands/channels/subscribe.ts Refactors channel subscribe flow to collect/await subscription “promises”.
src/commands/channels/presence/subscribe.ts Awaits presence subscription setup.
src/commands/channels/presence/enter.ts Awaits presence subscription setup when --show-others is enabled.
src/commands/channels/occupancy/subscribe.ts Awaits occupancy subscription setup.
src/commands/logs/subscribe.ts Collects and awaits log-type subscriptions.
src/commands/logs/push/subscribe.ts Awaits push log channel subscribe.
src/commands/logs/connection-lifecycle/subscribe.ts Awaits connection lifecycle subscribe.
src/commands/logs/channel-lifecycle/subscribe.ts Awaits channel lifecycle subscribe.
test/unit/utils/errors.test.ts Adds unit tests for error message extraction and hint lookup.
test/unit/commands/channels/subscribe.test.ts Adds capability-error test coverage for channel subscribe.
test/unit/commands/channels/presence/subscribe.test.ts Adds capability-error test coverage for presence subscribe.
test/unit/commands/channels/occupancy/subscribe.test.ts Adds capability-error test coverage for occupancy subscribe.
Comments suppressed due to low confidence (4)

src/commands/logs/push/subscribe.ts:92

  • await channel.subscribe(...) here is unlikely to be a reliable way to detect attach/capability/auth failures (the repo's Ably realtime mock subscribe returns void). If the intent is to fail the command with a friendly hint on capability errors, explicitly await channel.attach() (and let that throw/reject) before registering the subscription callback.
      // Subscribe to the channel
      await channel.subscribe((message) => {
        const timestamp = formatMessageTimestamp(message.timestamp);
        const event = message.name || "unknown";
        const logEvent = {
          channel: channelName,
          data: message.data,
          event,
          timestamp,
        };
        this.logCliEvent(
          flags,

src/commands/logs/connection-lifecycle/subscribe.ts:95

  • await channel.subscribe(...) is being used as if it will reject on attach/capability/auth failures, but in the project's Ably realtime mock subscribe is synchronous and does not return a Promise. If you need deterministic failure handling (to show friendly Ably error hints), explicitly await channel.attach() (and let that reject with ErrorInfo) before registering the subscription callback.
      // Subscribe to connection lifecycle logs
      await channel.subscribe((message: Ably.Message) => {
        const timestamp = formatMessageTimestamp(message.timestamp);
        const event = {
          timestamp,
          event: message.name || "connection.lifecycle",
          data: message.data,
          id: message.id,
        };

src/commands/logs/channel-lifecycle/subscribe.ts:98

  • await channel.subscribe(...) likely won't reliably wait for attachment or reject on capability/auth failures (the repo's realtime mock subscribe returns void). Consider explicitly await channel.attach() first so attach errors surface to the catch and can be formatted with friendly hints.
      // Subscribe to the channel
      await channel.subscribe((message) => {
        const timestamp = formatMessageTimestamp(message.timestamp);
        const event = message.name || "unknown";
        const logEvent = {
          channel: channelName,
          data: message.data,
          event,
          timestamp,

src/commands/channels/occupancy/subscribe.ts:120

  • await channel.subscribe(...) likely does not reliably wait for attachment or reject on capability/auth failures (the repo's realtime mock implementation of subscribe returns void). To make the new "capability error" handling deterministic, explicitly await channel.attach() in the try-block (so it can throw Ably ErrorInfo with code/status/href) and then register the subscription without relying on subscribe's return value.
      await channel.subscribe(occupancyEventName, (message: Ably.Message) => {
        const timestamp = formatMessageTimestamp(message.timestamp);
        const event = {
          channel: channelName,
          event: occupancyEventName,
          data: message.data,
          timestamp,
        };
        this.logCliEvent(
          flags,
          "occupancy",
          "occupancyUpdate",
          `Occupancy update received for channel ${channelName}`,
          event,
        );

        if (this.shouldOutputJson(flags)) {
          this.logJsonEvent(event, flags);
        } else {
          this.log(
            `${formatTimestamp(timestamp)} ${formatResource(`Channel: ${channelName}`)} | ${formatEventType("Occupancy Update")}`,
          );

          if (message.data !== null && message.data !== undefined) {
            this.log(
              `${formatLabel("Occupancy Data")} ${JSON.stringify(message.data, null, 2)}`,
            );
          }

          this.log(""); // Empty line for better readability
        }
      });

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

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: 5

🧹 Nitpick comments (3)
src/base-command.ts (2)

1507-1518: Use the resolved error code for both the hint and the banner.

The new hint lookup only checks cmdError.code, but the banner below already falls back to cmdError.context.errorCode. Reusing the same resolved code keeps those two outputs from diverging.

Suggested refactor
-    let humanMessage = cmdError.message;
-    const friendlyHint = getFriendlyAblyErrorHint(cmdError.code);
+    const code = cmdError.code ?? cmdError.context.errorCode;
+    let humanMessage = cmdError.message;
+    const friendlyHint = getFriendlyAblyErrorHint(code);
     if (friendlyHint) {
       humanMessage += `\n${friendlyHint}`;
     }
 
-    const code = cmdError.code ?? cmdError.context.errorCode;
     if (code !== undefined) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/base-command.ts` around lines 1507 - 1518, The friendly hint currently
uses cmdError.code directly which can diverge from the banner's resolved code;
resolve the code first (const code = cmdError.code ??
cmdError.context.errorCode) and pass that resolved code into
getFriendlyAblyErrorHint (e.g., getFriendlyAblyErrorHint(code)) so both the hint
and the banner use the same value, then build humanMessage and helpUrl logic
using that same code and cmdError.context.helpUrl.

940-943: Please add coverage for the stderr stream change.

This moves observable SDK error output from stdout to stderr in non-verbose, non-JSON mode. A CLI runner assertion on stderr() would lock the new contract in and prevent regressions.

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

In `@src/base-command.ts` around lines 940 - 943, Add a test that locks the new
contract by asserting the SDK error goes to stderr in non-verbose, non-JSON
mode: call the CLI entry point or the command class (the code path that invokes
logToStderr) with verbose=false and json=false, simulate or trigger an Ably SDK
error message, and assert the test runner's stderr() contains the string
"[AblySDK Error]" and the expected message while stdout remains unchanged; use
the existing CLI test helper/runner used elsewhere in tests to invoke the
command and check stderr() so the behavior of logToStderr is covered and will
prevent regressions.
test/unit/utils/errors.test.ts (1)

56-59: Simplify the undefined test case.

The [].pop() as number | undefined pattern is unnecessarily convoluted. TypeScript allows passing undefined directly since the function parameter likely accepts number | undefined.

✨ Simplified version
   it("should return undefined when code is not provided", () => {
-    const code = [].pop() as number | undefined;
-    expect(getFriendlyAblyErrorHint(code)).toBeUndefined();
+    expect(getFriendlyAblyErrorHint(undefined)).toBeUndefined();
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/unit/utils/errors.test.ts` around lines 56 - 59, The test "should return
undefined when code is not provided" uses an unnecessary construct ([] .pop() as
number | undefined); simplify it by passing undefined directly to
getFriendlyAblyErrorHint (e.g., set the test variable to undefined or call
getFriendlyAblyErrorHint(undefined)) so the assertion remains
expect(getFriendlyAblyErrorHint(code)).toBeUndefined(); this keeps the test
intent clear and removes the convoluted type trick.
🤖 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/channels/subscribe.ts`:
- Line 157: The mock Ably client's subscribe method currently returns undefined
(causing subscribe to behave differently than the real SDK), so update the mock
method (in test/helpers/mock-ably-realtime.ts — the mock's subscribe
implementation) to return a resolved Promise (e.g., return Promise.resolve() or
Promise.resolve<void>()) so it matches the real Ably SDK's Promise<void>
behavior and ensures subscribePromises (used in
src/commands/channels/subscribe.ts) await real resolution timing in tests.

In `@src/utils/errors.ts`:
- Around line 23-28: Update the capability hint strings for Ably error codes
40160, 40161, and 40171 in src/utils/errors.ts to use credential-neutral wording
(e.g., replace "API key" with "credential" or "token/key") so
getFriendlyAblyErrorHint() works for both token and API-key auth; ensure each
message still points users to check channel capability configuration in the Ably
dashboard and clearly references the capability restriction without assuming a
specific credential type.

In `@test/unit/commands/channels/occupancy/subscribe.test.ts`:
- Around line 127-137: The test's Error construction inside the Object.assign
uses a multi-line string which breaks Prettier; replace the multi-line new
Error(...) message with a single-line string literal so the Error is created as
new Error("Channel denied access based on given capability") within the existing
Object.assign expression (look for the Object.assign(...) containing new
Error(...) in subscribe.test.ts) and run Prettier to ensure formatting passes.

In `@test/unit/commands/channels/presence/subscribe.test.ts`:
- Around line 191-202: The multi-line error message passed into the Error
constructor inside the channel.presence.subscribe.mockRejectedValue call should
be a single-line string; update the Error(...) argument in the mocked rejection
(the Object.assign block used with channel.presence.subscribe.mockRejectedValue)
so the message "Channel denied access based on given capability" is on one line
to satisfy Prettier formatting.

In `@test/unit/commands/channels/subscribe.test.ts`:
- Around line 248-258: Prettier is failing because the Error instantiation uses
a multi-line string; update the Error construction inside the Object.assign call
so the message is a single-line string literal (i.e., change new Error("Channel
denied access based on given capability",) to a single-line new Error("Channel
denied access based on given capability")) within the test that contains the
Object.assign/new Error pattern to satisfy formatting.

---

Nitpick comments:
In `@src/base-command.ts`:
- Around line 1507-1518: The friendly hint currently uses cmdError.code directly
which can diverge from the banner's resolved code; resolve the code first (const
code = cmdError.code ?? cmdError.context.errorCode) and pass that resolved code
into getFriendlyAblyErrorHint (e.g., getFriendlyAblyErrorHint(code)) so both the
hint and the banner use the same value, then build humanMessage and helpUrl
logic using that same code and cmdError.context.helpUrl.
- Around line 940-943: Add a test that locks the new contract by asserting the
SDK error goes to stderr in non-verbose, non-JSON mode: call the CLI entry point
or the command class (the code path that invokes logToStderr) with verbose=false
and json=false, simulate or trigger an Ably SDK error message, and assert the
test runner's stderr() contains the string "[AblySDK Error]" and the expected
message while stdout remains unchanged; use the existing CLI test helper/runner
used elsewhere in tests to invoke the command and check stderr() so the behavior
of logToStderr is covered and will prevent regressions.

In `@test/unit/utils/errors.test.ts`:
- Around line 56-59: The test "should return undefined when code is not
provided" uses an unnecessary construct ([] .pop() as number | undefined);
simplify it by passing undefined directly to getFriendlyAblyErrorHint (e.g., set
the test variable to undefined or call getFriendlyAblyErrorHint(undefined)) so
the assertion remains expect(getFriendlyAblyErrorHint(code)).toBeUndefined();
this keeps the test intent clear and removes the convoluted type trick.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 676006c6-bb17-41cd-845b-57b8e786968d

📥 Commits

Reviewing files that changed from the base of the PR and between fc7c4c6 and 4aee569.

📒 Files selected for processing (14)
  • src/base-command.ts
  • src/commands/channels/occupancy/subscribe.ts
  • src/commands/channels/presence/enter.ts
  • src/commands/channels/presence/subscribe.ts
  • src/commands/channels/subscribe.ts
  • src/commands/logs/channel-lifecycle/subscribe.ts
  • src/commands/logs/connection-lifecycle/subscribe.ts
  • src/commands/logs/push/subscribe.ts
  • src/commands/logs/subscribe.ts
  • src/utils/errors.ts
  • test/unit/commands/channels/occupancy/subscribe.test.ts
  • test/unit/commands/channels/presence/subscribe.test.ts
  • test/unit/commands/channels/subscribe.test.ts
  • test/unit/utils/errors.test.ts

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.

2 participants