/output-error-http-client
Fix HTTP client misuse in Output SDK steps. Use when seeing untraced requests, missing error details, axios-related errors, or when HTTP calls aren't being properly logged and retried.
$ npx -y skills add growthxai/output --skill output-error-http-client --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/output-error-http-client
Context preview
The summary Claude sees to decide when to auto-load this skill.
Fix HTTP client misuse in Output SDK steps. Use when seeing untraced requests, missing error details, axios-related errors, or when HTTP calls aren't being properly logged and retried.
SKILL.md
output-error-http-client.SKILL.mdname: output-error-http-client
description: Fix HTTP client misuse in Output SDK steps. Use when seeing untraced requests, missing error details, axios-related errors, or when HTTP calls aren't being properly logged and retried.
allowed-tools: [Bash, Read]
Fix HTTP Client Misuse
Overview
This skill helps diagnose and fix issues caused by using axios, fetch, or other HTTP clients directly instead of Output SDK's `createKyClient` from `@outputai/http`. The Output SDK client provides tracing, automatic retries, and better error handling.
When to Use This Skill
You're seeing:
- Untraced HTTP requests (not appearing in workflow traces)
- Missing error details for failed requests
- axios-related errors or import issues
- Retries not working for HTTP failures
- Inconsistent timeout behavior
Root Cause
Using axios, fetch, or other HTTP clients directly bypasses Output SDK's:
- **Request/response tracing**: Calls aren't logged in workflow traces
- **Automatic retries**: Failed requests aren't retried
- **Error standardization**: Error formats may be inconsistent
- **Timeout handling**: Timeouts may not integrate with step timeouts
Symptoms
Using axios Directly
// WRONG: Using axios
import axios from 'axios';
export const fetchData = step( {
name: 'fetchData',
fn: async input => {
const response = await axios.get( 'https://api.example.com/data' );
return response.data;
}
} );Using fetch Directly
// WRONG: Using fetch
export const fetchData = step( {
name: 'fetchData',
fn: async input => {
const response = await fetch( 'https://api.example.com/data' );
return response.json();
}
} );Solution
Use `createKyClient` from `@outputai/http`:
Basic Usage
import { z, step } from '@outputai/core';
import { createKyClient } from '@outputai/http';
export const fetchData = step( {
name: 'fetchData',
inputSchema: z.object( {
endpoint: z.string()
} ),
outputSchema: z.object( {
data: z.unknown()
} ),
fn: async input => {
const client = createKyClient( {
prefix: 'https://api.example.com'
} );
const data = await client.get( input.endpoint ).json();
return { data };
}
} );With Full Configuration
import { createKyClient } from '@outputai/http';
const client = createKyClient( {
prefix: 'https://api.example.com',
timeout: 30000, // 30 second timeout
retry: {
limit: 3, // Retry up to 3 times
methods: [ 'GET', 'POST' ], // Which methods to retry
statusCodes: [ 408, 500, 502, 503, 504 ] // Which status codes trigger retry
},
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
}
} );HTTP Methods
GET Request
const data = await client.get( 'users/123' ).json();
POST Request
const result = await client.post( 'users', {
json: {
name: 'John',
email: 'john@example.com'
}
} ).json();PUT Request
const updated = await client.put( 'users/123', {
json: {
name: 'John Updated'
}
} ).json();DELETE Request
await client.delete( 'users/123' );
With Query Parameters
const data = await client.get( 'search', {
searchParams: {
q: 'query',
limit: 10
}
} ).json();Metadata-Only Responses
When code only reads metadata from a non-`HEAD` response, such as `response.url`, `response.status`, or headers, cancel the unused body. Reading a body with `.json()`, `.text()`, etc. already consumes it.
const response = await client.get( url );
try {
return response.url;
} finally {
await response.body?.cancel();
}Complete Migration Example
Before (Wrong - using axios)
import axios from 'axios';
import { step } from '@outputai/core';
export const createUser = step( {
name: 'createUser',
fn: async input => {
try {
const response = await axios.post(
'https://api.example.com/users',
{ name: input.name, email: input.email },
{
headers: { 'Authorization': `Bearer ${process.env.API_KEY}` },
timeout: 30000
}
);
return response.data;
} catch ( error ) {
if ( axios.isAxiosError( error ) ) {
throw new Error( `API Error: ${error.response?.data?.message}` );
}
throw error;
}
}
} );After (Correct - using createKyClient)
import { z, step } from '@outputai/core';
import { createKyClient } from '@outputai/http';
import { credentials } from '@outputai/credentials';
export const createUser = step( {
name: 'createUser',
inputSchema: z.object( {
name: z.string(),
email: z.string().email()
} ),
outputSchema: z.object( {
id: z.string(),
name: z.string(),
email: z.string()
} ),
fn: async input => {
const client = createKyClient( {
prefix: 'https://api.example.com',
timeout: 30000,
retry: { limit: 3 },
headers: {
'Authorization': `Bearer ${credentials.require( 'service.api_key' )}`
}
} );
const user = await client.post( 'users', {
json: {
name: input.name,
email: input.email
}
} ).json();
return user;
}
} );Error Handling
The Ky client provides structured error handling:
import { createKyClient, ky } from '@outputai/http';
export const fetchData = step( {
name: 'fetchData',
fn: async input => {
const client = createKyClient( { prefix: 'https://api.example.com' } );
try {
return await client.get( 'data' ).json();
} catch ( error ) {
if ( error instanceof ky.HTTPError ) {
// Access response details
const status = error.response.status;
const body = await error.response.json();
throw new Error( `API returned ${status}: ${body.message}` );
}
throw eRead more
name: output-error-http-client description: Fix HTTP client misuse in Output SDK steps. Use when seeing untraced requests, missing error details, axios-related errors, or when HTTP calls aren't being properly logged and retried. allowed-tools: [Bash, Read]
Fix HTTP Client Misuse
Overview
This skill helps diagnose and fix issues caused by using axios, fetch, or other HTTP clients directly instead of Output SDK's `createKyClient` from `@outputai/http`. The Output SDK client provides tracing, automatic retries, and better error handling.
When to Use This Skill
You're seeing:
- Untraced HTTP requests (not appearing in workflow traces)
- Missing error details for failed requests
- axios-related errors or import issues
- Retries not working for HTTP failures
- Inconsistent timeout behavior
Root Cause
Using axios, fetch, or other HTTP clients directly bypasses Output SDK's:
- **Request/response tracing**: Calls aren't logged in workflow traces
- **Automatic retries**: Failed requests aren't retried
- **Error standardization**: Error formats may be inconsistent
- **Timeout handling**: Timeouts may not integrate with step timeouts
Symptoms
Using axios Directly
// WRONG: Using axios
import axios from 'axios';
export const fetchData = step( {
name: 'fetchData',
fn: async input => {
const response = await axios.get( 'https://api.example.com/data' );
return response.data;
}
} );Using fetch Directly
// WRONG: Using fetch
export const fetchData = step( {
name: 'fetchData',
fn: async input => {
const response = await fetch( 'https://api.example.com/data' );
return response.json();
}
} );Solution
Use `createKyClient` from `@outputai/http`:
Basic Usage
import { z, step } from '@outputai/core';
import { createKyClient } from '@outputai/http';
export const fetchData = step( {
name: 'fetchData',
inputSchema: z.object( {
endpoint: z.string()
} ),
outputSchema: z.object( {
data: z.unknown()
} ),
fn: async input => {
const client = createKyClient( {
prefix: 'https://api.example.com'
} );
const data = await client.get( input.endpoint ).json();
return { data };
}
} );With Full Configuration
import { createKyClient } from '@outputai/http';
const client = createKyClient( {
prefix: 'https://api.example.com',
timeout: 30000, // 30 second timeout
retry: {
limit: 3, // Retry up to 3 times
methods: [ 'GET', 'POST' ], // Which methods to retry
statusCodes: [ 408, 500, 502, 503, 504 ] // Which status codes trigger retry
},
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
}
} );HTTP Methods
GET Request
const data = await client.get( 'users/123' ).json();
POST Request
const result = await client.post( 'users', {
json: {
name: 'John',
email: 'john@example.com'
}
} ).json();PUT Request
const updated = await client.put( 'users/123', {
json: {
name: 'John Updated'
}
} ).json();DELETE Request
await client.delete( 'users/123' );
With Query Parameters
const data = await client.get( 'search', {
searchParams: {
q: 'query',
limit: 10
}
} ).json();Metadata-Only Responses
When code only reads metadata from a non-`HEAD` response, such as `response.url`, `response.status`, or headers, cancel the unused body. Reading a body with `.json()`, `.text()`, etc. already consumes it.
const response = await client.get( url );
try {
return response.url;
} finally {
await response.body?.cancel();
}Complete Migration Example
Before (Wrong - using axios)
import axios from 'axios';
import { step } from '@outputai/core';
export const createUser = step( {
name: 'createUser',
fn: async input => {
try {
const response = await axios.post(
'https://api.example.com/users',
{ name: input.name, email: input.email },
{
headers: { 'Authorization': `Bearer ${process.env.API_KEY}` },
timeout: 30000
}
);
return response.data;
} catch ( error ) {
if ( axios.isAxiosError( error ) ) {
throw new Error( `API Error: ${error.response?.data?.message}` );
}
throw error;
}
}
} );After (Correct - using createKyClient)
import { z, step } from '@outputai/core';
import { createKyClient } from '@outputai/http';
import { credentials } from '@outputai/credentials';
export const createUser = step( {
name: 'createUser',
inputSchema: z.object( {
name: z.string(),
email: z.string().email()
} ),
outputSchema: z.object( {
id: z.string(),
name: z.string(),
email: z.string()
} ),
fn: async input => {
const client = createKyClient( {
prefix: 'https://api.example.com',
timeout: 30000,
retry: { limit: 3 },
headers: {
'Authorization': `Bearer ${credentials.require( 'service.api_key' )}`
}
} );
const user = await client.post( 'users', {
json: {
name: input.name,
email: input.email
}
} ).json();
return user;
}
} );Error Handling
The Ky client provides structured error handling:
import { createKyClient, ky } from '@outputai/http';
export const fetchData = step( {
name: 'fetchData',
fn: async input => {
const client = createKyClient( { prefix: 'https://api.example.com' } );
try {
return await client.get( 'data' ).json();
} catch ( error ) {
if ( error instanceof ky.HTTPError ) {
// Access response details
const status = error.response.status;
const body = await error.response.json();
throw new Error( `API returned ${status}: ${body.message}` );
}
throw eThe open-source TypeScript framework for building AI workflows and agents. Designed for Claude Code — describe what you want, Claude builds it, with all the best practices already in place. One framework.
Repo: growthxai/output
Other skills on output.
- /llm-output-schema-constraints
Zod schema constraints that Anthropic rejects or silently ignores when sent as structured-output tool definitions via Output.object(). Use when writing or reviewing Zod schemas passed to Output.object(), or debugging structured-output validation errors.
Open skill - /prompt-file-provider-options
Guide to the providerOptions structure in .prompt files — decision tree for where an option goes, common mistakes, per-provider quick reference, and Anthropic prompt caching. Use when writing or reviewing .prompt file frontmatter (provider, model, providerOptions,
Open skill - /validate
Run lint, build, and tests to validate changes are correct
Open skill - /output-build-workflow
Implement an Output SDK workflow from a plan document. Use when the user asks to build, implement, or code a workflow from an existing plan, or after output-plan-workflow has produced a plan and the user is ready to build.
Open skill - /output-credentials-edit
View and edit encrypted credentials in an Output.ai project. Use when adding secrets, updating API keys, verifying credential values, or retrieving a specific credential.
Open skill - /output-credentials-env-vars
Wire encrypted credentials to environment variables using the credential: convention. Use when setting up LLM provider keys (ANTHROPIC_API_KEY, OPENAI_API_KEY) or any env var that should come from encrypted credentials.
Open skill

