bytedance/deer-flow · error · GitHubAppAuthError

Neither GITHUB_APP_PRIVATE_KEY nor GITHUB_APP_PRIVATE_KEY_PA

Error message

Neither GITHUB_APP_PRIVATE_KEY nor GITHUB_APP_PRIVATE_KEY_PATH is set

What it means

GitHubAppAuthError raised by load_app_private_key() when neither GITHUB_APP_PRIVATE_KEY (inline PEM) nor GITHUB_APP_PRIVATE_KEY_PATH is set. The App's RSA private key is required to sign the App JWT used for installation tokens; inline takes precedence over the path form.

Source

Thrown at backend/app/gateway/github/app_auth.py:110

    except ValueError as exc:
        raise GitHubAppAuthError(f"{_APP_ID_ENV}={raw!r} is not an integer") from exc


def load_app_private_key() -> str:
    """Return the App's RSA private key as a PEM string.

    Reads from ``GITHUB_APP_PRIVATE_KEY`` (inline PEM) if set, else from
    the path in ``GITHUB_APP_PRIVATE_KEY_PATH``. Inline takes precedence
    so operators can roll a key by setting an env var instead of moving
    files around in production.
    """
    inline = os.environ.get(_PRIVATE_KEY_ENV)
    if inline and inline.strip():
        return inline

    path = os.environ.get(_PRIVATE_KEY_PATH_ENV)
    if not path:
        raise GitHubAppAuthError(f"Neither {_PRIVATE_KEY_ENV} nor {_PRIVATE_KEY_PATH_ENV} is set")
    p = Path(path).expanduser()
    if not p.exists():
        raise GitHubAppAuthError(f"{_PRIVATE_KEY_PATH_ENV} points to nonexistent file: {p}")
    return p.read_text(encoding="utf-8")


def mint_app_jwt(*, now: float | None = None) -> str:
    """Sign a short-lived JWT identifying this App to GitHub.

    Args:
        now: Optional override for ``time.time()`` — tests use this.

    Returns:
        Signed RS256 JWT suitable for ``Authorization: Bearer <jwt>``.
    """
    issued_at = int(now if now is not None else time.time())
    payload = {
        # GitHub recommends iat 60s in the past to tolerate clock skew.

View on GitHub (pinned to 1dd6ba1acb)

Solutions

  1. Download the App's private key (.pem) from GitHub App settings (Generate a private key)
  2. Either set GITHUB_APP_PRIVATE_KEY to the full inline PEM (including BEGIN/END lines, e.g. via a mounted secret) or set GITHUB_APP_PRIVATE_KEY_PATH to the mounted file path
  3. Prefer _PATH with a mounted secret file in containers; inline via env suits ephemeral rollouts
  4. Verify: `docker exec <gateway> sh -c 'wc -l $GITHUB_APP_PRIVATE_KEY_PATH && head -1 $GITHUB_APP_PRIVATE_KEY_PATH'` should show a BEGIN PRIVATE KEY header

Example fix

# before: only id set
GITHUB_APP_ID=861753                 # -> GitHubAppAuthError: Neither ..._KEY nor ..._PATH is set
# after
GITHUB_APP_ID=861753
GITHUB_APP_PRIVATE_KEY_PATH=/run/secrets/gh_app_key.pem
Defensive patterns

Strategy: validation

Validate before calling

import os, pathlib
key = os.environ.get('GITHUB_APP_PRIVATE_KEY')
path = os.environ.get('GITHUB_APP_PRIVATE_KEY_PATH')
if not (key and key.strip()) and not path:
    raise ConfigError('GitHub App private key not configured')

Type guard

null

Try / catch

except GitHubAppAuthError as e:
    if 'Neither GITHUB_APP_PRIVATE_KEY' in str(e):
        raise ConfigError('Generate the App .pem and set inline var or _PATH')
    raise

Prevention

When it happens

Trigger: Any attempt to mint an App JWT / installation token with both key env vars absent — i.e. the GitHub App integration is partially configured (App id present, key missing).

Common situations: Operator downloaded the .pem but only set GITHUB_APP_ID; secrets mounted into the container but env var pointing at them never added; moving from PAT-based to App-based auth and forgetting the key step; key stored only on the operator workstation, not in the deployment environment.

Related errors


AI-assisted analysis of bytedance/deer-flow@1dd6ba1acb (2026-08-14). Data as JSON: /api/errors/d1bbcd175d272c04. Report an issue: GitHub.