{"record":{"id":"5e30516031ba4406","repo":"bytedance/deer-flow","slug":"github-app-private-key-path-points-to-nonexistent","errorCode":null,"errorMessage":"GITHUB_APP_PRIVATE_KEY_PATH points to nonexistent file: {p}","messagePattern":"GITHUB_APP_PRIVATE_KEY_PATH points to nonexistent file: (.+?)","errorType":"exception","errorClass":"GitHubAppAuthError","httpStatus":null,"severity":"error","filePath":"backend/app/gateway/github/app_auth.py","lineNumber":113,"sourceCode":"\ndef load_app_private_key() -> str:\n    \"\"\"Return the App's RSA private key as a PEM string.\n\n    Reads from ``GITHUB_APP_PRIVATE_KEY`` (inline PEM) if set, else from\n    the path in ``GITHUB_APP_PRIVATE_KEY_PATH``. Inline takes precedence\n    so operators can roll a key by setting an env var instead of moving\n    files around in production.\n    \"\"\"\n    inline = os.environ.get(_PRIVATE_KEY_ENV)\n    if inline and inline.strip():\n        return inline\n\n    path = os.environ.get(_PRIVATE_KEY_PATH_ENV)\n    if not path:\n        raise GitHubAppAuthError(f\"Neither {_PRIVATE_KEY_ENV} nor {_PRIVATE_KEY_PATH_ENV} is set\")\n    p = Path(path).expanduser()\n    if not p.exists():\n        raise GitHubAppAuthError(f\"{_PRIVATE_KEY_PATH_ENV} points to nonexistent file: {p}\")\n    return p.read_text(encoding=\"utf-8\")\n\n\ndef mint_app_jwt(*, now: float | None = None) -> str:\n    \"\"\"Sign a short-lived JWT identifying this App to GitHub.\n\n    Args:\n        now: Optional override for ``time.time()`` — tests use this.\n\n    Returns:\n        Signed RS256 JWT suitable for ``Authorization: Bearer <jwt>``.\n    \"\"\"\n    issued_at = int(now if now is not None else time.time())\n    payload = {\n        # GitHub recommends iat 60s in the past to tolerate clock skew.\n        \"iat\": issued_at - 60,\n        \"exp\": issued_at + _APP_JWT_TTL_SECONDS,\n        # iss must be a string in current pyjwt; GitHub accepts the","sourceCodeStart":95,"sourceCodeEnd":131,"githubUrl":"https://github.com/bytedance/deer-flow/blob/1dd6ba1acb03700589994b0366c5d1c7d05e2eff/backend/app/gateway/github/app_auth.py#L95-L131","documentation":"GitHubAppAuthError raised by load_app_private_key() when GITHUB_APP_PRIVATE_KEY_PATH is set but the file does not exist at the expanded path. Path is expanduser()-ed, so '~' works; existence is checked before read_text().","triggerScenarios":"Calling load_app_private_key() with a _PATH value pointing to a missing file — secret not mounted, wrong mount path in compose/K8s, file only present on the host but the Gateway runs in a container, or a typo in the path.","commonSituations":"Docker bind-mount uses a relative path resolved against a different workdir; Kubernetes secret volume name mismatch; the pem was never copied to the server; container runs as a different user so ~ expands elsewhere; trailing whitespace/newline in the env var corrupting the path.","solutions":["Check the path from the process's point of view: `docker exec <gateway> ls -l \"$GITHUB_APP_PRIVATE_KEY_PATH\"`","Fix the mount: compose volumes: - ./secrets/gh.pem:/run/secrets/gh.pem:ro and point _PATH at /run/secrets/gh.pem; in K8s verify the secret and volumeMount agree","Strip stray whitespace/newlines from the env value (common with .env files edited on Windows)","Remember ~ expands to the container user's home, not yours — prefer absolute paths in containers"],"exampleFix":"# before (compose)\nenvironment:\n  GITHUB_APP_PRIVATE_KEY_PATH: ~/secrets/gh.pem   # not mounted -> GitHubAppAuthError\n# after\nvolumes:\n  - ./secrets/gh.pem:/run/secrets/gh.pem:ro\nenvironment:\n  GITHUB_APP_PRIVATE_KEY_PATH: /run/secrets/gh.pem","handlingStrategy":"validation","validationCode":"import os, pathlib\np = pathlib.Path(os.environ['GITHUB_APP_PRIVATE_KEY_PATH']).expanduser()\nif not p.is_file():\n    raise ConfigError(f'private key file missing: {p}')\nkey = p.read_text()\nassert 'PRIVATE KEY' in key, 'file is not a PEM'","typeGuard":"null","tryCatchPattern":"except GitHubAppAuthError as e:\n    if 'nonexistent file' in str(e):\n        fix_mount_and_restart()  # mount check, then redeploy\n    raise","preventionTips":["Use absolute container paths for secrets, never ~ or relative paths","Smoke-test secret mounts (`ls -l $PATH`) in the container after deploy","Trim whitespace from env values in .env files"],"tags":["github-app","file-not-found","secrets","docker","kubernetes"],"backgroundTag":null,"analyzedSha":"1dd6ba1acb03700589994b0366c5d1c7d05e2eff","analyzedAt":"2026-08-14T21:20:34.804Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}