headroomlabs-ai/headroom · error · RecommendationsError

recommendations file not found: {0}

Error message

recommendations file not found: {0}

What it means

The non-object-JSON rejection in _read_json_body_with_bytes (helpers.py:2551), the shared reader behind the Anthropic, OpenAI, and Bedrock request paths. The body is valid JSON but its root is not an object (list/str/int/bool/null), and these APIs require an object.

Source

Thrown at crates/headroom-core/src/transforms/recommendations.rs:252

/// Module-level convenience: look up a recommendation in the global
/// store. PR-F3 will wire this into `dispatch_compressor`. PR-B5 only
/// exposes the API surface.
pub fn get(
    auth_mode: AuthMode,
    model: &str,
    structure_hash: &str,
) -> Option<&'static Recommendation> {
    load_default().lookup(auth_mode, model, structure_hash)
}

/// Errors surfaced by the loader. Marked non-exhaustive so we can add
/// future variants without breaking callers.
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum RecommendationsError {
    /// File doesn't exist on disk.
    #[error("recommendations file not found: {0}")]
    Missing(PathBuf),
    /// Filesystem error other than NotFound.
    #[error("recommendations IO error at {path}: {source}")]
    Io {
        path: PathBuf,
        #[source]
        source: std::io::Error,
    },
    /// TOML parse failure (typed wrapper for ergonomics).
    #[error("recommendations TOML parse error: {0}")]
    Parse(#[from] toml::de::Error),
}

#[cfg(test)]
mod tests {
    use super::*;

    fn sample_toml() -> &'static str {

View on GitHub (pinned to 322425c43b)

Solutions

  1. Send a top-level JSON object with the expected schema keys
  2. Guard client-side: assert isinstance(json.loads(text), dict)
  3. Remove wrapping middleware that envelopes payloads in arrays

Example fix

// before (js)
fetch(url, {body: JSON.stringify([msg])})
// after
fetch(url, {body: JSON.stringify({model, messages: [msg]})})
Defensive patterns

Strategy: type-guard

Validate before calling

assert isinstance(payload, dict), f"request must be a JSON object, got {type(payload).__name__}"

Type guard

def is_request_object(v: object) -> TypeGuard[dict]:
    return isinstance(v, dict)

Try / catch

try:
    result, raw = await _read_json_body_with_bytes(request)
except ValueError as exc:
    return JSONResponse({"error": str(exc)}, status_code=400)

Prevention

When it happens

Trigger: Sending an array-wrapped or scalar JSON body to any main proxied chat route (Anthropic /v1/messages, OpenAI-compatible /v1/chat/completions, Bedrock translation path).

Common situations: Batch-style clients, double-serialized strings, or fixture reuse from other APIs hitting the primary proxy paths.

Related errors


AI-assisted analysis of headroomlabs-ai/headroom@322425c43b (2026-08-15). Data as JSON: /api/errors/3e0256af5fcb8aae. Report an issue: GitHub.