vercel/turborepo · warning
GITHUB_TOKEN/GH_TOKEN contains invalid characters, ignoring.
Error message
GITHUB_TOKEN/GH_TOKEN contains invalid characters, ignoring.
What it means
The turbo-utils example downloader reads a GitHub token from GITHUB_TOKEN (or GH_TOKEN) to authenticate requests to api.github.com/codeload.github.com. Before use, the token is validated against /[\r\n\u0000]/; if it contains a carriage return, line feed, or NUL character, the token is considered malformed and is silently discarded (this message is only a warning). The download then proceeds unauthenticated, which can surface later as GitHub rate-limit or auth-failed warnings. The check exists because control characters in an Authorization header enable header-injection and are never part of a valid PAT.
Source
Thrown at packages/turbo-utils/src/examples.ts:31
const REQUEST_TIMEOUT = 10000;
const DOWNLOAD_TIMEOUT = 120000;
// Hosts that receive Authorization headers when GITHUB_TOKEN / GH_TOKEN is set.
// Limited to GitHub.com API hosts. GitHub Enterprise Server is not yet supported.
const GITHUB_API_HOSTS = new Set(["api.github.com", "codeload.github.com"]);
/**
* Reads a GitHub personal access token from the environment.
* GITHUB_TOKEN takes precedence over GH_TOKEN, matching the GitHub CLI convention.
* Requires `repo` scope (classic PAT) or `contents:read` (fine-grained PAT).
*/
function getGitHubToken(): string | undefined {
const token = (process.env.GITHUB_TOKEN || process.env.GH_TOKEN || "").trim();
if (!token) return undefined;
// eslint-disable-next-line no-control-regex -- Intentional: reject tokens containing control characters
if (/[\r\n\u0000]/.test(token)) {
warn("GITHUB_TOKEN/GH_TOKEN contains invalid characters, ignoring.");
return undefined;
}
return token;
}
/**
* Returns an Authorization header for GitHub API requests when a token
* is available. Only sends tokens to hosts in GITHUB_API_HOSTS to
* prevent credential leakage to third-party domains.
*/
function getGitHubAuthHeaders(url: string): Record<string, string> {
try {
const { hostname } = new URL(url);
if (!GITHUB_API_HOSTS.has(hostname)) {
return {};
}
} catch {
return {};View on GitHub (pinned to f9245100cf)
Solutions
- Re-set the secret/environment value with no trailing newline: `printf '%s' 'ghp_xxx'` instead of echo, or re-paste the PAT into the CI secret field and confirm no line break.
- If using an .env file created on Windows, convert line endings (dos2unix) or rewrite the file so GITHUB_TOKEN sits on a single LF-terminated line.
- Verify the token is clean before running: `node -e "const t=process.env.GITHUB_TOKEN||process.env.GH_TOKEN;console.log(/[\r\n\u0000]/.test(t||''))"` — it must print false.
- Regenerate the PAT at github.com/settings/tokens if the stored value itself got corrupted, and update the secret.
Example fix
# before (adds trailing newline -> token rejected) echo ghp_xxxxxxxx >> ~/.bashrc export GITHUB_TOKEN="ghp_xxxxxxxx\n" # after (value stays on one line, no newline inside) printf 'export GITHUB_TOKEN=%s\n' 'ghp_xxxxxxxx' >> ~/.bashrc export GITHUB_TOKEN=ghp_xxxxxxxx
Defensive patterns
Strategy: validation
Validate before calling
const TOKEN_RE = /^[A-Za-z0-9_-]+$/; // ghp_/github_pat_ tokens are URL-safe, no newlines/NULs
const raw = process.env.GITHUB_TOKEN || process.env.GH_TOKEN || "";
if (raw && !TOKEN_RE.test(raw.trim())) {
console.error("GITHUB_TOKEN/GH_TOKEN has invalid characters; re-set the secret without newlines.");
process.exit(1); // fail fast before create-turbo runs
} Type guard
function isValidGitHubToken(value: string | undefined): value is string {
if (!value) return false;
const t = value.trim();
return t.length > 0 && !/[\r\n\u0000]/.test(t);
} Prevention
- Never create token secrets with echo >>; use printf or the CI UI so no trailing newline is embedded.
- Add a preflight check in CI that fails the job if /[\r\n\u0000]/ matches the token, instead of silently losing auth.
- Keep .env files LF-only (editorconfig insert_final_newline applies to the file, not values); run dos2unix on files authored on Windows.
- Note the library only warns: an ignored token degrades to unauthenticated GitHub requests and can later look like rate-limiting — check for this warning first.
When it happens
Trigger: getGitHubToken() returns non-empty process.env.GITHUB_TOKEN or GH_TOKEN whose value contains \r, \n, or \u0000 — e.g. a CI secret saved with a trailing newline, a Windows CRLF-terminated .env line, a multi-line pasted value, or a value quoted across lines in a shell profile. Then getGitHubAuthHeaders()/isUrlOk/downloadAndExtractExample log this warning and send no Authorization header.
Common situations: A GitHub Actions/Docker secret defined with a trailing newline (classic on Windows runners or editors adding CRLF); an .env file written on Windows and committed with CRLF line endings; a token copied with an embedded line break from a password manager; exporting the token with `echo token >> ~/.bashrc` so a stray newline lands inside the value.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- GitHub auth failed (HTTP ${res.status}). Check GITHUB_TOKEN/
- Failed to download: ${response.status}
- Remote cache is read-only, skipping upload
- {pad}• {base_msg}
- Git download failed: ${formatError(gitError)} Falling back t
AI-assisted analysis of vercel/turborepo@f9245100cf (2026-08-17).
Data as JSON: /api/errors/98901aed094a6464.
Report an issue: GitHub.