Install any skill in seconds. Free to start, no credit card required.
Get Started Free →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.
.claude/skills/growthxai-output-error-http-client/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 38% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 45% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 140% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 47% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 90% | 0% |
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.
You're seeing:
Using axios, fetch, or other HTTP clients directly bypasses Output SDK's:
typescript// 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; } } );
typescript// WRONG: Using fetch export const fetchData = step( { name: 'fetchData', fn: async input => { const response = await fetch( 'https://api.example.com/data' ); return response.json(); } } );
Use createKyClient from @outputai/http:
typescriptimport { 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 }; } } );
typescriptimport { 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' } } );
typescriptconst data = await client.get( 'users/123' ).json();
typescriptconst result = await client.post( 'users', { json: { name: 'John', email: 'john@example.com' } } ).json();
typescriptconst updated = await client.put( 'users/123', { json: { name: 'John Updated' } } ).json();
typescriptawait client.delete( 'users/123' );
typescriptconst data = await client.get( 'search', { searchParams: { q: 'query', limit: 10 } } ).json();
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.
typescriptconst response = await client.get( url ); try { return response.url; } finally { await response.body?.cancel(); }
typescriptimport 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; } } } );
typescriptimport { 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; } } );
The Ky client provides structured error handling:
typescriptimport { 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 error; } } } );
Search your codebase:
bash# Find axios imports grep -rn "from 'axios'\|from \"axios\"" src/ # Find fetch calls grep -rn "await fetch(" src/ # Find other HTTP libraries grep -rn "got\|node-fetch\|request\|superagent" src/
| Option | Description | Default | |--------|-------------|---------| | prefix | Base URL for all requests | (required) | | timeout | Request timeout in ms | 10000 | | retry.limit | Max retry attempts | 2 | | retry.methods | HTTP methods to retry | 'GET', 'PUT', 'HEAD', 'DELETE', 'OPTIONS', 'TRACE'] | | retry.statusCodes | Status codes to retry | 408, 413, 429, 500, 502, 503, 504] | | headers | Default headers | {} |
After migrating to createKyClient:
npx output workflow run <name> --input '<input>'npx output workflow debug <id> --jsonoutput-error-direct-iooutput-services-checkoutput-dev-credentials| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-19 | pass→pass | 14,482 | 5,908 | -59% | 1 | 1 | 0% | 2,601 | 3,204 | +23% | 0 | 0 | — |
case-20 | pass→pass | 10,420 | 5,618 | -46% | 1 | 1 | 0% | 1,589 | 3,085 | +94% | 0 | 0 | — |
case-21 | pass→pass | 9,652 | 4,690 | -51% | 1 | 1 | 0% | 1,570 | 2,948 | +88% | 0 | 0 | — |
case-01 | fail→pass | 12,103 | 5,207 | -57% | 1 | 1 | 0% | 2,377 | 3,270 | +38% | 0 | 0 | — |
case-02 | fail→pass | 11,398 | 6,294 | -45% | 1 | 1 | 0% | 2,406 | 3,494 | +45% | 0 | 0 | — |
case-03 | fail→pass | 7,872 | 6,383 | -19% | 1 | 1 | 0% | 1,501 | 3,602 | +140% | 0 | 0 | — |
case-04 | pass→pass | 10,641 | 4,738 | -55% | 1 | 1 | 0% | 1,997 | 3,083 | +54% | 0 | 0 | — |
case-05 | fail→pass | 9,021 | 3,375 | -63% | 1 | 1 | 0% | 1,957 | 2,874 | +47% | 0 | 0 | — |
case-06 | pass→pass | 12,912 | 3,128 | -76% | 1 | 1 | 0% | 2,475 | 2,727 | +10% | 0 | 0 | — |
case-07 | fail→pass | 6,566 | 3,099 | -53% | 1 | 1 | 0% | 1,418 | 2,699 | +90% | 0 | 0 | — |
case-08 | fail→pass | 7,555 | 2,850 | -62% | 1 | 1 | 0% | 1,336 | 2,724 | +104% | 0 | 0 | — |
case-09 | fail→pass | 9,739 | 4,780 | -51% | 1 | 1 | 0% | 1,825 | 3,069 | +68% | 0 | 0 | — |
case-10 | fail→pass | 10,160 | 2,584 | -75% | 1 | 1 | 0% | 2,095 | 2,495 | +19% | 0 | 0 | — |
case-11 | pass→pass | 6,559 | 2,174 | -67% | 1 | 1 | 0% | 1,281 | 2,523 | +97% | 0 | 0 | — |
case-12 | fail→pass | 9,896 | 4,464 | -55% | 1 | 1 | 0% | 1,659 | 3,020 | +82% | 0 | 0 | — |
case-13 | pass→pass | 5,951 | 2,900 | -51% | 1 | 1 | 0% | 1,115 | 2,626 | +136% | 0 | 0 | — |
case-14 | pass→pass | 7,151 | 2,629 | -63% | 1 | 1 | 0% | 1,263 | 2,534 | +101% | 0 | 0 | — |
case-15 | pass→pass | 12,488 | 4,628 | -63% | 1 | 1 | 0% | 2,323 | 2,993 | +29% | 0 | 0 | — |
case-16 | pass→pass | 6,633 | 1,611 | -76% | 1 | 1 | 0% | 979 | 2,355 | +141% | 0 | 0 | — |
case-17 | pass→pass | 4,905 | 1,579 | -68% | 1 | 1 | 0% | 735 | 2,315 | +215% | 0 | 0 | — |
case-18 | pass→pass | 8,074 | 2,647 | -67% | 1 | 1 | 0% | 1,344 | 2,583 | +92% | 0 | 0 | — |
case-22 | fail→pass | 9,448 | 7,237 | -23% | 1 | 1 | 0% | 1,900 | 3,232 | +70% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +45 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.