odysseus-dev/odysseus · error · HTTPException

Invalid token characters

Error message

Invalid token characters

What it means

HTTP 400 from _validate_token() in routes/cookbook_helpers.py when a non-empty token fails _TOKEN_RE. Hugging Face tokens are restricted to a conservative character set (alphanumerics and typical token punctuation); spaces, quotes, shell metacharacters, or stray characters cause rejection. Empty/None is allowed (returns None).

Source

Thrown at routes/cookbook_helpers.py:91

        raise HTTPException(400, "repo_id is required")
    if _REPO_ID_RE.match(v) or _LOCAL_MODEL_ID_RE.match(v) or _OLLAMA_MODEL_ID_RE.match(v):
        return v
    raise HTTPException(400, "Invalid repo_id — must be <org>/<name>, an Ollama name:tag, or a cached local model id")


def _validate_include(v: str | None) -> str | None:
    if v is None or v == "":
        return None
    if not _INCLUDE_RE.match(v):
        raise HTTPException(400, "Invalid include pattern")
    return v


def _validate_token(v: str | None) -> str | None:
    if v is None or v == "":
        return None
    if not _TOKEN_RE.match(v):
        raise HTTPException(400, "Invalid token characters")
    return v


def load_stored_hf_token(*, state_path: Path | str | None = None) -> str:
    """Return the decrypted HF token from cookbook_state.json, else env fallback."""
    path = Path(state_path) if state_path else Path(os.environ.get("DATA_DIR", "data")) / "cookbook_state.json"
    token = ""
    if path.exists():
        try:
            state = json.loads(path.read_text(encoding="utf-8"))
            env = state.get("env") if isinstance(state, dict) else {}
            if isinstance(env, dict) and env.get("hfToken"):
                from src.secret_storage import decrypt
                token = decrypt(env.get("hfToken") or "")
        except Exception:
            token = ""
    if not token:
        token = (os.environ.get("HF_TOKEN") or os.environ.get("HUGGING_FACE_HUB_TOKEN") or "").strip()

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Send only the raw token string, no quotes or whitespace — trim it client-side first
  2. Re-copy the token from your HF settings page in case it was truncated or mangled
  3. Check _TOKEN_RE in routes/cookbook_helpers.py for the exact allowed characters

Example fix

// before
token = `"${process.env.HF_TOKEN}"`  // sends wrapping quotes
// after
token = process.env.HF_TOKEN.trim()
Defensive patterns

Strategy: validation

Validate before calling

import re
TOKEN_RE = re.compile(r"^[A-Za-z0-9_.\-]+$")  # conservative mirror of _TOKEN_RE

def clean_token(v):
    if v is None or v == "":
        return None
    v = v.strip().strip('"').strip("'")
    if not TOKEN_RE.match(v):
        raise ValueError("token contains invalid characters after trimming")
    return v

Prevention

When it happens

Trigger: Sending token="hf_abc123 " (trailing space), a token with a newline from clipboard paste, a quoted token (\"hf_...\"), or a token containing $, ;, |, or backticks.

Common situations: Copy-pasting the token with surrounding whitespace or quotes; pasting an entire `huggingface-cli login` command instead of just the token; a truncated token that no longer matches the expected charset.

Understand the failure class

Related errors


AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14). Data as JSON: /api/errors/02eec35e77e703f2. Report an issue: GitHub.