How to Debug Common headroom.js Issues: A Complete Troubleshooting Guide
Most headroom.js runtime problems stem from three layers—proxy server misconfiguration, client mode settings, or content-specific header handling—and can be diagnosed using debug logging, environment variables, and the built-in validate_setup() helper.
The headroom.js JavaScript client (distributed as the headroom-ai npm package) wraps the same compression pipeline that powers the Python and Rust implementations in the chopratejas/headroom repository. When token savings disappear, proxies refuse connections, or beta headers persist unexpectedly, systematic debugging of these three layers quickly isolates the root cause.
Understanding the Three Layers of headroom.js Debugging
Headroom’s architecture separates concerns into distinct layers. Identifying which layer produces the symptom accelerates your fix.
Proxy and Server Issues
Symptoms manifest as headroom proxy startup failures, "connection refused" errors, or HTTP 502/503 responses.
Diagnosis steps:
- Launch the proxy with verbose logging:
headroom proxy --log-level debug - Check for port-conflict messages (e.g., "addr already in use")
- Verify required environment variables like
OPENAI_API_KEYare exported
Resolution:
- Change the listening port using
--port 8788if the default is occupied - Install missing extras:
npm install headroom-aiorpip install "headroom-ai[proxy]" - Restart the proxy to clear in-memory trackers (see Session Beta Header Tracking below)
Client Configuration Problems
When you observe zero token savings, overly aggressive compression, or high latency, the client configuration is typically misaligned.
Diagnosis steps:
- Inspect the runtime mode:
client.get_stats()['config']['mode'] - Check which transforms are enabled via
smart_crusher_enabled - Measure timing by setting
logging.basicConfig(level=logging.DEBUG)
Resolution:
- Set mode to
"optimize"(the default is"audit", which performs no compression) - Adjust
smart_crusherthresholds: modifymin_tokens_to_crushandmax_items_after_crush - Disable unused transforms:
cache_aligner.enabled = false
Content-Specific Bugs
Missing tool output, validation errors on setup, or unexpected header injection indicate content-layer issues.
Diagnosis steps:
- Enable beta-header sticky debugging:
HEADROOM_BETA_HEADER_STICKY=debug - Run the
validate_setup()helper to surface schema errors - Inspect request/response logs for missing
anthropic-betaor custom headers
Resolution:
- Turn off header-sticky behavior:
HEADROOM_BETA_HEADER_STICKY=disabled - Re-run
validate_setup()and fix configuration mismatches - Pass
headroom_tool_profilesfor tools that must not be compressed
Step-by-Step Debugging Workflow for JavaScript
Execute this terminal workflow to isolate issues systematically.
Start the proxy in debug mode and verify health:
# Start proxy with detailed logging
headroom proxy --log-level debug &
PROXY_PID=$!
# Verify proxy is listening (adjust port if using --port)
curl -s http://localhost:8787/health && echo "✅ proxy up"
Run a minimal JavaScript test with explicit configuration:
import { HeadroomClient, OpenAIProvider } from 'headroom-ai';
import { OpenAI } from 'openai';
const client = new HeadroomClient({
originalClient: new OpenAI({ apiKey: process.env.OPENAI_API_KEY }),
provider: new OpenAIProvider(),
defaultMode: 'optimize', // Ensure compression is active
});
(async () => {
const resp = await client.chat.completions.create({
model: 'gpt-4o',
messages: [{ role: 'user', content: 'Hello' }],
});
console.log('✅ response', resp.choices[0].message.content);
console.log('Stats', client.getStats());
})();
If tokens_saved_total reports zero:
- Confirm mode is
"optimize"(not"audit") - Check
smart_crusher.min_tokens_to_crush(default is 200 tokens) - Verify the request contains tool output (compression only runs on tool data)
If requests fail with 502/503: Inspect the proxy's debug log for stack traces:
cat $(headroom proxy --log-dir)/headroom.log | grep -iE 'error|stack|smartcrusher'
Common headroom.js Issues and Their Fixes
| Symptom | Root Cause | Fix |
|---|---|---|
| Proxy hangs on startup | Port already bound by stale process | Run lsof -i :8787 to find the PID, kill it, or start on a different port (--port 8788). |
| "Connection refused" errors | Proxy not running or wrong port | Run headroom proxy --log-level debug and verify curl http://localhost:8787/health. |
| Beta-header token persists | SessionBetaTracker re-injects old anthropic-beta values |
Set HEADROOM_BETA_HEADER_STICKY=disabled or restart the proxy to clear the tracker. |
| Zero token savings | Client in "audit" mode or smart_crusher disabled/threshold too high |
Set defaultMode: "optimize" and lower smart_crusher.min_tokens_to_crush. |
| Missing information in LLM response | Compression too aggressive (max_items_after_crush=15) |
Increase smart_crusher.max_items_after_crush (e.g., to 50) or disable compression for that tool via headroom_tool_profiles. |
| High latency | Embedding-based relevance scoring or low thresholds | Switch to BM25 (config.smart_crusher.relevance.tier = "bm25"), raise the token threshold, or disable unused transforms (cache_aligner.enabled = false). |
Navigating the Source Code
When debugging requires source-level inspection, refer to these specific files in the chopratejas/headroom repository:
sdk/typescript/src/client.ts— ExposesHeadroomClient, wraps LLM providers, and handles request-level mode toggles viadefaultMode.sdk/typescript/src/errors.ts— Defines custom error types that surface in JavaScript stack traces.sdk/typescript/src/types/config.ts— Shapes theHeadroomConfigobject used by the client (e.g.,smart_crusherthresholds).sdk/typescript/src/utils/format.ts— Formats diagnostic messages when--log-level debugis set.crates/headroom-proxy/src/handlers/mod.rs— Core Rust proxy handlers exposing/debug/*endpoints used by the JavaScript client.wiki/troubleshooting.md— Canonical source of diagnostic steps.
Summary
- Start with proxy debug logs: Running
headroom proxy --log-level debugreveals exactly where compression or routing decisions fail. - Verify client mode and transforms: The JavaScript SDK defaults to
"audit"mode (no savings) and aggressive compression settings that may require tuning. - Use
validate_setup(): This built-in helper catches misconfigured API keys and malformed profiles before runtime. - Check header handling: Set
HEADROOM_BETA_HEADER_STICKY=disabledwhen encountering persistent beta-header issues.
When these checks pass, the proxy's /debug/* endpoints (available only from 127.0.0.1) provide snapshots of internal state for deeper investigation.
Frequently Asked Questions
Why is headroom.js showing zero token savings?
The JavaScript client defaults to "audit" mode, which calculates savings without actually compressing requests. Set defaultMode: "optimize" in your HeadroomClient constructor, and ensure your request contains tool output (the compressor only processes tool data). Also verify that smart_crusher.min_tokens_to_crush (default 200) is not set higher than your actual token count.
How do I fix "connection refused" when starting the headroom proxy?
This error indicates the proxy is not running or is listening on a different port. Run headroom proxy --log-level debug to confirm startup, then verify with curl http://localhost:8787/health. If the port is occupied by a stale process, use lsof -i :8787 to identify and kill the process, or specify an alternative port with --port 8788.
What causes persistent anthropic-beta headers in my requests?
The SessionBetaTracker in the proxy re-injects cached anthropic-beta values across sessions. To disable this behavior, set the environment variable HEADROOM_BETA_HEADER_STICKY=disabled. Alternatively, restart the proxy to clear the in-memory tracker state.
How can I reduce latency when using headroom.js?
High latency typically stems from embedding-based relevance scoring or excessively low compression thresholds. Switch to BM25 via config.smart_crusher.relevance.tier = "bm25", raise min_tokens_to_crush, or disable unused transforms like cache_aligner by setting cache_aligner.enabled = false.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →