{"record":{"id":"02eec35e77e703f2","repo":"odysseus-dev/odysseus","slug":"invalid-token-characters","errorCode":null,"errorMessage":"Invalid token characters","messagePattern":"Invalid token characters","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"routes/cookbook_helpers.py","lineNumber":91,"sourceCode":"        raise HTTPException(400, \"repo_id is required\")\n    if _REPO_ID_RE.match(v) or _LOCAL_MODEL_ID_RE.match(v) or _OLLAMA_MODEL_ID_RE.match(v):\n        return v\n    raise HTTPException(400, \"Invalid repo_id — must be <org>/<name>, an Ollama name:tag, or a cached local model id\")\n\n\ndef _validate_include(v: str | None) -> str | None:\n    if v is None or v == \"\":\n        return None\n    if not _INCLUDE_RE.match(v):\n        raise HTTPException(400, \"Invalid include pattern\")\n    return v\n\n\ndef _validate_token(v: str | None) -> str | None:\n    if v is None or v == \"\":\n        return None\n    if not _TOKEN_RE.match(v):\n        raise HTTPException(400, \"Invalid token characters\")\n    return v\n\n\ndef load_stored_hf_token(*, state_path: Path | str | None = None) -> str:\n    \"\"\"Return the decrypted HF token from cookbook_state.json, else env fallback.\"\"\"\n    path = Path(state_path) if state_path else Path(os.environ.get(\"DATA_DIR\", \"data\")) / \"cookbook_state.json\"\n    token = \"\"\n    if path.exists():\n        try:\n            state = json.loads(path.read_text(encoding=\"utf-8\"))\n            env = state.get(\"env\") if isinstance(state, dict) else {}\n            if isinstance(env, dict) and env.get(\"hfToken\"):\n                from src.secret_storage import decrypt\n                token = decrypt(env.get(\"hfToken\") or \"\")\n        except Exception:\n            token = \"\"\n    if not token:\n        token = (os.environ.get(\"HF_TOKEN\") or os.environ.get(\"HUGGING_FACE_HUB_TOKEN\") or \"\").strip()","sourceCodeStart":73,"sourceCodeEnd":109,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/routes/cookbook_helpers.py#L73-L109","documentation":"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).","triggerScenarios":"Sending token=\"hf_abc123 \" (trailing space), a token with a newline from clipboard paste, a quoted token (\\\"hf_...\\\"), or a token containing $, ;, |, or backticks.","commonSituations":"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.","solutions":["Send only the raw token string, no quotes or whitespace — trim it client-side first","Re-copy the token from your HF settings page in case it was truncated or mangled","Check _TOKEN_RE in routes/cookbook_helpers.py for the exact allowed characters"],"exampleFix":"// before\ntoken = `\"${process.env.HF_TOKEN}\"`  // sends wrapping quotes\n// after\ntoken = process.env.HF_TOKEN.trim()","handlingStrategy":"validation","validationCode":"import re\nTOKEN_RE = re.compile(r\"^[A-Za-z0-9_.\\-]+$\")  # conservative mirror of _TOKEN_RE\n\ndef clean_token(v):\n    if v is None or v == \"\":\n        return None\n    v = v.strip().strip('\"').strip(\"'\")\n    if not TOKEN_RE.match(v):\n        raise ValueError(\"token contains invalid characters after trimming\")\n    return v","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Trim whitespace and strip wrapping quotes before sending","Never send the whole `huggingface-cli login` line — only the token","Read tokens from a secret store, not from pasted shell history"],"tags":["validation","huggingface","token","http-400"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}