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

  1. 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.
  2. 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.
  3. 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.
  4. 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

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

Related errors


AI-assisted analysis of vercel/turborepo@f9245100cf (2026-08-17). Data as JSON: /api/errors/98901aed094a6464. Report an issue: GitHub.