vercel/ai · error · HarnessCapabilityUnsupportedError

Harness 'claude-code' requires a JSON schema for structured

Error message

Harness 'claude-code' requires a JSON schema for structured output.

What it means

The claude-code harness maps structured output onto the Claude CLI's schema support, which requires a JSON schema to constrain the model's response. When a prompt turn requests `responseFormat: { type: 'json' }` without a `schema`, the harness throws HarnessCapabilityUnsupportedError because free-form 'json' output cannot be reliably produced.

Source

Thrown at packages/harness-claude-code/src/claude-code-harness.ts:1735

        ? {}
        : {
            submitUserMessage: async (text: string) => {
              await userMessageSubmitter.submit(text);
            },
          }),
      done,
    };
  };

  return {
    sessionId,
    isResume,
    doPromptTurn: async promptOpts => {
      if (
        promptOpts.responseFormat?.type === 'json' &&
        promptOpts.responseFormat.schema == null
      ) {
        throw new HarnessCapabilityUnsupportedError({
          message:
            "Harness 'claude-code' requires a JSON schema for structured output.",
          harnessId: 'claude-code',
        });
      }
      await writeClaudeCodeSkills({
        sandbox,
        homeDir: sandboxHomeDir,
        skills: promptOpts.skills,
        abortSignal: promptOpts.abortSignal,
      });
      const control = wireTurn({
        emit: promptOpts.emit,
        abortSignal: promptOpts.abortSignal,
      });

      /*
       * A signal that was already aborted has settled the turn inside

View on GitHub (pinned to 69428b1f8b)

Solutions

  1. Always supply a schema when using `responseFormat: { type: 'json' }` (a JSON Schema object describing the expected output).
  2. If you do not need structured output, remove the responseFormat option entirely instead of using type 'json' without a schema.
  3. Use a zod schema converted with the SDK's jsonSchema/zodSchema helper to generate the schema value.

Example fix

// before
await session.prompt({ prompt: 'List users', responseFormat: { type: 'json' } });
// after
await session.prompt({
  prompt: 'List users',
  responseFormat: { type: 'json', schema: usersJsonSchema },
});
Defensive patterns

Strategy: validation

Validate before calling

function assertJsonResponseFormat(fmt) {
  if (fmt && fmt.type === 'json' && fmt.schema == null) {
    throw new Error('responseFormat.type=json requires a schema');
  }
}
assertJsonResponseFormat(promptOpts.responseFormat);

Type guard

function hasJsonSchema(
  rf: { type: 'json'; schema?: unknown } | undefined,
): rf is { type: 'json'; schema: NonNullable<unknown> } {
  return rf?.type === 'json' && rf.schema != null;
}

Try / catch

try {
  await harness.prompt({ prompt, responseFormat });
} catch (e) {
  if (HarnessCapabilityUnsupportedError.isInstance(e) && e.message.includes('JSON schema')) {
    // retry with a schema or without responseFormat
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling the harness prompt path (doPromptTurn) with `responseFormat: { type: 'json' }` where `responseFormat.schema` is null or undefined.

Common situations: Porting code from another harness/provider that accepts schema-less JSON mode; building responseFormat dynamically and omitting schema; SDK type changes after an upgrade where schema became mandatory for json type.

Related errors


AI-assisted analysis of vercel/ai@69428b1f8b (2026-08-30). Data as JSON: /api/errors/4f02fb9ad4336c44. Report an issue: GitHub.