Skip to main content

At most one HTTP client

ab-000225 · ai-slop-code-drift.tooling-stack-drift.dual-http-client
Severity: highactive

Why it matters

Two HTTP clients create two error-handling conventions, two interceptor pipelines, two retry policies, and two authentication header-injection paths. A token refresh that works in your axios interceptor silently skips every ky call, producing inconsistent 401 behavior that depends on which file made the request. Request/response logging captures only half your traffic, and timeout defaults differ (axios has no default timeout, ky defaults to 10 seconds) creating unpredictable production behavior.

Severity rationale

High because inconsistent auth-header injection and timeout behavior produce hard-to-reproduce production incidents.

Remediation

Wrap your chosen client once and import only that wrapper everywhere.

// src/lib/http.ts
import ky from 'ky'
export const http = ky.extend({
  prefixUrl: '/api',
  timeout: 10000,
  hooks: { beforeRequest: [addAuthHeader] },
})

Rewrite every axios call site to use import { http } from '@/lib/http', then npm uninstall axios. Add an ESLint rule forbidding direct imports of both libraries outside src/lib/http.ts.

Detection

  • ID: ai-slop-code-drift.tooling-stack-drift.dual-http-client

  • Severity: high

  • What to look for: Apply the three-condition rule against this exact HTTP client allowlist: axios, ky, got, node-fetch, cross-fetch, isomorphic-fetch, superagent, request (deprecated), phin, undici. Note that fetch (built into Node 18+ and all browsers) is NOT a package — do not count it. Count all REMAINING libraries from the allowlist. EXCEPT: if the project legitimately needs both browser fetch and a Node-side library (e.g., undici for HTTP/2 streaming), check if the Node-side library is isolated to a specific server-only directory like app/api/ or server/ AND is only imported by ≤3 files — if so, treat it as an escape hatch and pass.

  • Pass criteria: 0 or 1 HTTP client libraries from the allowlist actively used. Report even on pass: "Canonical HTTP client: [name or 'fetch (built-in)'] ([N] importing files)."

  • Fail criteria: 2 or more HTTP client libraries from the allowlist meet all three conditions (at least 1 non-escape-hatch importing file) and the escape-hatch exception does not apply.

  • Skip (N/A) when: 0 HTTP client libraries from the allowlist appear in RUNTIME_DEPS.

  • Detail on fail: "2 active HTTP clients: 'axios' (15 files) AND 'ky' (6 files). Pick one and convert the rest."

  • Remediation: Two HTTP clients means two error-handling patterns, two interceptor systems, two retry strategies. Pick one and migrate:

    // Bad: some files use axios, others use ky
    // Good: a single src/lib/http.ts wrapper around your chosen client
    
    // src/lib/http.ts
    import ky from 'ky'
    export const http = ky.extend({ prefixUrl: '/api' })
    
    // Then everywhere else:
    import { http } from '@/lib/http'
    

    After migration, npm uninstall axios.

Taxons

History