fix(claude-code): prevent "Right-hand side of instanceof is not an object" when SDK missing; improve CLI stability#1146
Conversation
🦋 Changeset detectedLatest commit: 3da5d42 The changes in this PR will be included in the next version bump. 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 |
WalkthroughAdds defensive checks in Claude Code provider: ensures SDK presence before calling query, guards AbortError instanceof checks, and updates optional dependency @anthropic-ai/claude-code to ^1.0.88. Includes a changeset describing the fix. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor CLI as CLI
participant Provider as ClaudeCodeProvider
participant SDK as Claude Code SDK (optional)
CLI->>Provider: generate()/stream()
alt SDK not loaded
Provider-->>CLI: Throw error "Claude Code SDK not available"
else SDK loaded
Provider->>SDK: query(request)
SDK-->>Provider: response | error
alt AbortError (if defined)
Provider-->>CLI: propagate/cancel
else Other error
Provider-->>CLI: wrap and return error
end
end
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes
Suggested reviewers
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate unit tests
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
Status, Documentation and Community
|
Add defensive checks to prevent "Right-hand side of 'instanceof' is not an object" errors when the optional Claude Code SDK is not installed. Changes: - Check if AbortError exists before using instanceof - Check if query function exists before calling it - Provide clear error messages when SDK is missing This fixes the issue reported by users in v0.24.0 and v0.25.0 where Task Master would crash with instanceof errors when using the claude-code provider without the SDK installed.
60fcd3d to
7c85c01
Compare
7c85c01 to
3da5d42
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
package.json (1)
13-27: Add the missing test script aliases to align with repo conventions.Per coding guidelines, please include test:unit, test:integration, and test:ci script entries (they can proxy to your jest invocations).
Example:
{ "scripts": { "test": "node --experimental-vm-modules node_modules/.bin/jest", "test:watch": "node --experimental-vm-modules node_modules/.bin/jest --watch", "test:coverage": "node --experimental-vm-modules node_modules/.bin/jest --coverage", "test:unit": "node --experimental-vm-modules node_modules/.bin/jest --testPathPattern=tests/unit", "test:integration": "node --experimental-vm-modules node_modules/.bin/jest --testPathPattern=tests/integration", "test:e2e": "./tests/e2e/run_e2e.sh", "test:ci": "npm run format-check && npm run test -- --ci --reporters=default --reporters=jest-junit" } }src/ai-providers/custom-sdk/claude-code/language-model.js (2)
440-487: Bug: ‘error’ is referenced inside try-block without being defined; this will throw ReferenceError and break successful streams.The JSON-truncation fallback lives inside the try block and inspects error, which doesn’t exist in this scope. That turns every successful stream into a ReferenceError, caught by the catch and emitted as an error. Additionally, accumulatedText and usage are declared inside try, making them unavailable to the catch where they are actually needed for graceful recovery.
Apply this refactor to hoist state, remove the invalid try-block check, and implement truncation recovery inside catch:
@@ - const stream = new ReadableStream({ + // Hoist to share between try/catch for truncation recovery + let usage = { promptTokens: 0, completionTokens: 0 }; + let accumulatedText = ''; + + const stream = new ReadableStream({ start: async (controller) => { try { @@ - let usage = { promptTokens: 0, completionTokens: 0 }; - let accumulatedText = ''; + // usage and accumulatedText are hoisted above @@ - // ------------------------------------------------------------- - // Work-around for Claude-Code CLI/SDK JSON truncation bug (#913) - // ------------------------------------------------------------- - // If we hit the SDK JSON SyntaxError but have buffered text, finalize - // the stream gracefully instead of emitting an error. - const isJsonTruncation = - error instanceof SyntaxError && - /JSON/i.test(error.message || '') && - (error.message.includes('position') || - error.message.includes('Unexpected end')); - - if ( - isJsonTruncation && - accumulatedText && - accumulatedText.length > 0 - ) { - // Prepare final text payload - const finalText = - options.mode?.type === 'object-json' - ? extractJson(accumulatedText) - : accumulatedText; - - // Emit any remaining text - controller.enqueue({ - type: 'text-delta', - textDelta: finalText - }); - - // Emit finish with truncated reason and warning - controller.enqueue({ - type: 'finish', - finishReason: 'truncated', - usage, - providerMetadata: { 'claude-code': { truncated: true } }, - warnings: [ - { - type: 'provider-warning', - details: - 'Claude Code SDK JSON truncation detected; stream recovered.' - } - ] - }); - - controller.close(); - return; // Skip normal error path - } - controller.close(); } catch (error) { - let errorToEmit; + // JSON truncation fallback (mirrors doGenerate) + const isJsonTruncation = + error instanceof SyntaxError && + /JSON/i.test(error.message || '') && + (error.message.includes('position') || + error.message.includes('Unexpected end')); + if (isJsonTruncation && accumulatedText && accumulatedText.length > 0) { + const finalText = + options.mode?.type === 'object-json' + ? extractJson(accumulatedText) + : accumulatedText; + controller.enqueue({ type: 'text-delta', textDelta: finalText }); + controller.enqueue({ + type: 'finish', + finishReason: 'truncated', + usage, + providerMetadata: { 'claude-code': { truncated: true } }, + warnings: [ + { + type: 'provider-warning', + details: + 'Claude Code SDK emitted a JSON parse error but stream recovered buffered text (possible CLI truncation).' + } + ] + }); + controller.close(); + return; // Skip normal error emission + } + + let errorToEmit;This restores the intended behavior: recover when truncation occurs, otherwise propagate structured errors.
491-515: Stream abort handling is now safe; keep it. Add a unit test that simulates missing AbortError.The guarded instanceof protects against SDK variance. Please add a unit test where the SDK mock exports query but omits AbortError to assert no RHS instanceof crash is thrown and aborts still propagate correctly.
I can draft a jest test using a mocked @anthropic-ai/claude-code module that only exports query and triggers an abort signal to ensure the guard works. Want me to open a follow-up PR with that?
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (3)
.changeset/quiet-owls-fix-cli-sdk.md(1 hunks)package.json(1 hunks)src/ai-providers/custom-sdk/claude-code/language-model.js(4 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
.changeset/*.md
📄 CodeRabbit inference engine (.cursor/rules/changeset.mdc)
.changeset/*.md: When runningnpm run changesetornpx changeset add, provide a concise summary of the changes for theCHANGELOG.mdin imperative mood, typically a single line, and not a detailed Git commit message.
The changeset summary should be user-facing, describing what changed in the released version that is relevant to users or consumers of the package.
Do not use your detailed Git commit message body as the changeset summary.
Files:
.changeset/quiet-owls-fix-cli-sdk.md
.changeset/*
📄 CodeRabbit inference engine (.cursor/rules/new_features.mdc)
Create appropriate changesets for new features, use semantic versioning, include tagged system information in release notes, and document breaking changes if any.
Files:
.changeset/quiet-owls-fix-cli-sdk.md
**/*.js
📄 CodeRabbit inference engine (.cursor/rules/tests.mdc)
**/*.js: Declare and initialize global variables at the top of modules to avoid hoisting issues.
Use proper function declarations to avoid hoisting issues and initialize variables before they are referenced.
Do not reference variables before their declaration in module scope.
Use dynamic imports (import()) to avoid initialization order issues in modules.
Files:
src/ai-providers/custom-sdk/claude-code/language-model.js
package.json
📄 CodeRabbit inference engine (.cursor/rules/test_workflow.mdc)
Add and update test scripts in package.json to include test, test:watch, test:coverage, test:unit, test:integration, test:e2e, and test:ci
Files:
package.json
🧠 Learnings (2)
📚 Learning: 2025-07-18T17:06:04.909Z
Learnt from: CR
PR: eyaltoledano/claude-task-master#0
File: .cursor/rules/ai_providers.mdc:0-0
Timestamp: 2025-07-18T17:06:04.909Z
Learning: Applies to src/ai-providers/*.js : Implement generate<ProviderName>Text, stream<ProviderName>Text, and generate<ProviderName>Object functions in provider modules with basic validation and try/catch error handling.
Applied to files:
src/ai-providers/custom-sdk/claude-code/language-model.js
📚 Learning: 2025-08-07T13:00:22.966Z
Learnt from: Crunchyman-ralph
PR: eyaltoledano/claude-task-master#1090
File: apps/extension/package.json:241-243
Timestamp: 2025-08-07T13:00:22.966Z
Learning: In monorepos, local packages should use "*" as the version constraint in package.json dependencies, as recommended by npm. This ensures the local version from within the same workspace is always used, rather than attempting to resolve from external registries. This applies to packages like task-master-ai within the eyaltoledano/claude-task-master monorepo.
Applied to files:
package.json
🪛 LanguageTool
.changeset/quiet-owls-fix-cli-sdk.md
[grammar] ~5-~5: Use correct spacing
Context: ...1.0.88` for improved export consistency. Related to JSON truncation handling in #...
(QB_NEW_EN_OTHER_ERROR_IDS_5)
[grammar] ~7-~7: There might be a mistake here.
Context: ...Related to JSON truncation handling in #920; this change addresses a separate error-...
(QB_NEW_EN_OTHER)
[grammar] ~7-~7: Use correct spacing
Context: ...rate error-path crash reported in #1142.
(QB_NEW_EN_OTHER_ERROR_IDS_5)
🪛 markdownlint-cli2 (0.17.2)
.changeset/quiet-owls-fix-cli-sdk.md
5-5: First line in a file should be a top-level heading
(MD041, first-line-heading, first-line-h1)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: test
🔇 Additional comments (2)
package.json (1)
89-89: Optional SDK bump confirmed—please verify exports
The npm registry shows that@anthropic-ai/claude-code@1.0.88is published (bothlatestandnexttags) and matches your version bump. However, importing the module in this environment (without first installing it) resulted inERR_MODULE_NOT_FOUND.Please install
@anthropic-ai/claude-code@^1.0.88in your project and confirm that it exports the expected symbols (e.g.,queryandAbortError) before merging. For example:npm install @anthropic-ai/claude-code@^1.0.88 node -e "import('@anthropic-ai/claude-code').then(m => console.log(Object.keys(m))).catch(e => { console.error(e); process.exit(1) })"src/ai-providers/custom-sdk/claude-code/language-model.js (1)
235-239: Guarded AbortError instanceof is correct and prevents RHS exceptions.Good defensive check: AbortError && error instanceof AbortError avoids “Right-hand side of 'instanceof' is not an object” when the optional SDK omits AbortError.
| fix(claude-code): prevent crash/hang when the optional `@anthropic-ai/claude-code` SDK is missing by guarding `AbortError instanceof` checks and adding explicit SDK presence checks in `doGenerate`/`doStream`. Also bump the optional dependency to `^1.0.88` for improved export consistency. | ||
|
|
||
| Related to JSON truncation handling in #920; this change addresses a separate error-path crash reported in #1142. |
There was a problem hiding this comment.
🧹 Nitpick (assertive)
Condense to a single, user-facing imperative summary per changeset guidelines.
The current message is verbose. Keep it concise and in imperative mood.
Apply:
-fix(claude-code): prevent crash/hang when the optional `@anthropic-ai/claude-code` SDK is missing by guarding `AbortError instanceof` checks and adding explicit SDK presence checks in `doGenerate`/`doStream`. Also bump the optional dependency to `^1.0.88` for improved export consistency.
+fix(claude-code): prevent crash when optional `@anthropic-ai/claude-code` is missing by guarding AbortError instanceof and checking SDK presence; bump optional dep to ^1.0.88.Optional: Keep the second paragraph referencing #920 and #1142 for context; the summary line itself should remain short.
📝 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.
| fix(claude-code): prevent crash/hang when the optional `@anthropic-ai/claude-code` SDK is missing by guarding `AbortError instanceof` checks and adding explicit SDK presence checks in `doGenerate`/`doStream`. Also bump the optional dependency to `^1.0.88` for improved export consistency. | |
| Related to JSON truncation handling in #920; this change addresses a separate error-path crash reported in #1142. | |
| fix(claude-code): prevent crash when optional `@anthropic-ai/claude-code` is missing by guarding AbortError instanceof and checking SDK presence; bump optional dep to ^1.0.88. | |
| Related to JSON truncation handling in #920; this change addresses a separate error-path crash reported in #1142. |
🧰 Tools
🪛 LanguageTool
[grammar] ~5-~5: Use correct spacing
Context: ...1.0.88` for improved export consistency. Related to JSON truncation handling in #...
(QB_NEW_EN_OTHER_ERROR_IDS_5)
[grammar] ~7-~7: There might be a mistake here.
Context: ...Related to JSON truncation handling in #920; this change addresses a separate error-...
(QB_NEW_EN_OTHER)
[grammar] ~7-~7: Use correct spacing
Context: ...rate error-path crash reported in #1142.
(QB_NEW_EN_OTHER_ERROR_IDS_5)
🪛 markdownlint-cli2 (0.17.2)
5-5: First line in a file should be a top-level heading
(MD041, first-line-heading, first-line-h1)
🤖 Prompt for AI Agents
.changeset/quiet-owls-fix-cli-sdk.md lines 5-7: Replace the verbose
multi-sentence summary with one concise, user-facing imperative sentence (e.g.,
"Guard against missing @anthropic-ai/claude-code SDK to prevent crash/hang and
bump optional dependency to ^1.0.88."); optionally retain the second paragraph
referencing #920 and #1142 below the single-line summary for context.
| if (!query) { | ||
| throw new Error( | ||
| "Claude Code SDK is not installed. Please install '@anthropic-ai/claude-code' to use the claude-code provider." | ||
| ); | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick (assertive)
Centralize SDK-missing validation to the loader to avoid duplicated guards.
This inline guard is fine, but you already call loadClaudeCodeModule(). Consider moving the “SDK missing/invalid exports” error into that loader so all call sites are covered uniformly, and you don’t need per-call checks.
A minimal tweak in the loader:
async function loadClaudeCodeModule() {
if (!query || !AbortError) {
try {
const mod = await import('@anthropic-ai/claude-code');
query = mod.query;
AbortError = mod.AbortError;
if (typeof query !== 'function') {
throw new Error("Claude Code SDK export 'query' is missing.");
}
} catch {
throw new Error(
"Claude Code SDK is not installed. Please install '@anthropic-ai/claude-code' to use the claude-code provider."
);
}
}
}If you adopt this, you can safely remove the inline if (!query) checks here and in doStream.
🤖 Prompt for AI Agents
In src/ai-providers/custom-sdk/claude-code/language-model.js around lines 172 to
176, the inline guard that throws when the Claude Code SDK is missing should be
centralized into loadClaudeCodeModule() so all call sites share the same
validation; modify loadClaudeCodeModule() to import the module, assign query and
AbortError, and validate that query (and other required exports) exist and are
the correct types, throwing the SDK-missing or specific export-missing error
there, then remove the redundant if (!query) checks from this file (and
doStream) so callers assume loadClaudeCodeModule() ensures the SDK is present
and valid.
| if (!query) { | ||
| throw new Error( | ||
| "Claude Code SDK is not installed. Please install '@anthropic-ai/claude-code' to use the claude-code provider." | ||
| ); | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick (assertive)
Duplicate SDK-missing guard — safe but unnecessary if the loader is authoritative.
Same note as in doGenerate: prefer a single source of truth in loadClaudeCodeModule() to reduce drift and ensure consistent messaging.
🤖 Prompt for AI Agents
In src/ai-providers/custom-sdk/claude-code/language-model.js around lines 343 to
347, there is a duplicate guard that throws an SDK-missing error; remove this
redundant check and any throw here and rely on loadClaudeCodeModule() as the
single authoritative loader/error source, or if you prefer explicit safety,
replace this throw with a simple null-check that returns/propagates the original
loader error (do not duplicate messaging). Ensure callers expect the loader to
either return a valid module or throw so message text stays consistent and only
lives in loadClaudeCodeModule().
…ject" when SDK missing; improve CLI stability (#1146) * fix: handle missing @anthropic-ai/claude-code SDK gracefully Add defensive checks to prevent "Right-hand side of 'instanceof' is not an object" errors when the optional Claude Code SDK is not installed. Changes: - Check if AbortError exists before using instanceof - Check if query function exists before calling it - Provide clear error messages when SDK is missing This fixes the issue reported by users in v0.24.0 and v0.25.0 where Task Master would crash with instanceof errors when using the claude-code provider without the SDK installed. * chore: bump @anthropic-ai/claude-code to ^1.0.88 and regenerate lockfile
…ject" when SDK missing; improve CLI stability (eyaltoledano#1146) * fix: handle missing @anthropic-ai/claude-code SDK gracefully Add defensive checks to prevent "Right-hand side of 'instanceof' is not an object" errors when the optional Claude Code SDK is not installed. Changes: - Check if AbortError exists before using instanceof - Check if query function exists before calling it - Provide clear error messages when SDK is missing This fixes the issue reported by users in v0.24.0 and v0.25.0 where Task Master would crash with instanceof errors when using the claude-code provider without the SDK installed. * chore: bump @anthropic-ai/claude-code to ^1.0.88 and regenerate lockfile
…ject" when SDK missing; improve CLI stability (eyaltoledano#1146) * fix: handle missing @anthropic-ai/claude-code SDK gracefully Add defensive checks to prevent "Right-hand side of 'instanceof' is not an object" errors when the optional Claude Code SDK is not installed. Changes: - Check if AbortError exists before using instanceof - Check if query function exists before calling it - Provide clear error messages when SDK is missing This fixes the issue reported by users in v0.24.0 and v0.25.0 where Task Master would crash with instanceof errors when using the claude-code provider without the SDK installed. * chore: bump @anthropic-ai/claude-code to ^1.0.88 and regenerate lockfile
Summary: Guard abort checks and add explicit SDK presence checks in the Claude Code provider to avoid crashes/hangs when the optional SDK is not installed or partially exported. Provides clear guidance to users and keeps the CLI responsive.
Fixes: Fixes bug: Claude Code Provider broken? #1142
Related: Complements fix(claude-code): recover from CLI JSON truncation bug (#913) #920 (JSON parse/truncation workaround). fix(claude-code): recover from CLI JSON truncation bug (#913) #920 handled truncated JSON from the CLI/SDK; this PR addresses a separate crash when handling errors if the SDK export is missing.
Root Cause: The optional dependency
@anthropic-ai/claude-codemay be absent (omitted optional deps, proxy/install issues), or its exports may differ by environment. WhenAbortErroris undefined, using it on the right-hand side ofinstanceofthrows “Right-hand side of 'instanceof' is not an object.” Some users never hit the catch path (so it “works for me”), others do and crash.Changes:
instanceofchecks: useAbortError && error instanceof AbortError.!querychecks indoGenerateanddoStreamwith a clear message to install@anthropic-ai/claude-code.@anthropic-ai/claude-code->^1.0.88to reduce export inconsistencies and ensure current SDK behavior.Affected Code:
src/ai-providers/custom-sdk/claude-code/language-model.js(minimal, focused changes)Verification:
node_modules/@anthropic-ai/claude-codeor install with--no-optional; runparse-prdusing theclaude-codeprovider — now returns a clear install message instead of crashing.instanceofunder failure paths.Tests:
tests/integration/claude-code-optional.test.jstests/unit/ai-providers/custom-sdk/claude-code/language-model.test.jsquerywithoutAbortErrorto assert guard correctness.Risk:
Release note:
@anthropic-ai/claude-codeis missing by guardingAbortErrorchecks and providing a clear install message; complements prior JSON truncation fix.Summary by CodeRabbit