{"record":{"id":"a1eea9a05a3b1a82","repo":"paperclipai/paperclip","slug":"managed-github-connection-is-unavailable","errorCode":null,"errorMessage":"Managed GitHub connection is unavailable","messagePattern":"Managed GitHub connection is unavailable","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/src/services/git-credentials.ts","lineNumber":238,"sourceCode":"    secrets?: GitCredentialSecretsDeps;\n    env?: NodeJS.ProcessEnv;\n    secretNames?: readonly string[];\n  },\n): GitRemoteAuthProvider {\n  const secrets: GitCredentialSecretsDeps = deps?.secrets ?? secretService(db);\n  const env = deps?.env ?? process.env;\n  const secretNames = deps?.secretNames ?? DEFAULT_GITHUB_TOKEN_SECRET_NAMES;\n  let credentialPromise: Promise<GitCredential | null> | null = null;\n\n  const resolveCredential = async (): Promise<GitCredential | null> => {\n    // Unit callers historically pass a null DB through the typed test seam. Production\n    // always supplies a real DB and therefore always checks managed identities before\n    // considering legacy secrets or process environment credentials.\n    const managed = db\n      ? await resolveManagedGitHubCredential(db, secrets, companyId, context ?? {})\n      : { configured: false as const };\n    if (managed.configured) {\n      if (!managed.credential) throw new Error(managed.error ?? \"Managed GitHub connection is unavailable\");\n      return managed.credential;\n    }\n    for (const secretName of secretNames) {\n      const secret = await Promise.resolve(secrets.getByName(companyId, secretName)).catch(() => null);\n      if (!secret) continue;\n      // A resolution failure (inactive secret, provider outage) records its own failure audit\n      // event; fall through to the next source instead of failing the whole git operation here.\n      const token = await secrets\n        .resolveSecretValue(companyId, secret.id, \"latest\", {\n          accessContext: {\n            consumerType: \"system\",\n            consumerId: \"workspace-git-credential\",\n            actorType: \"system\",\n            issueId: context?.issueId ?? null,\n            heartbeatRunId: context?.heartbeatRunId ?? null,\n            responsibleUserId: context?.responsibleUserId ?? null,\n          },\n        })","sourceCodeStart":220,"sourceCodeEnd":256,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/server/src/services/git-credentials.ts#L220-L256","documentation":"During git credential resolution, Paperclip checks the managed GitHub identity first. If a managed GitHub connection is configured (selected as the identity source) but a usable credential cannot be produced, `resolveManagedGitHubCredential` returns `configured: true` with an `error` message instead of a credential. `resolveCredential` then fails closed and throws this error rather than silently falling back to legacy company secrets or the server's GITHUB_TOKEN/GH_TOKEN environment variables.","triggerScenarios":"Calling `createGitRemoteAuthProvider(db, companyId, context)(remoteUrl)` (or `resolveCredential`) when: (1) a managed GitHub connection exists for the company but the OAuth grant is missing/expired and refresh via `refreshOAuthGrantCredentials` fails; (2) the identity owner is not an active non-viewer company member; (3) the grant's credential secret ref for `oauth.access_token` or the GitHub tenant record is missing ('identity is incomplete'); (4) the GitHub app installation or repository access was revoked (installationCount/repositoryCount < 1); (5) the personal credential secret cannot be resolved.","commonSituations":"A GitHub App installation was uninstalled from the org; the user who owns a personal GitHub connection left the company or was downgraded to viewer; the OAuth token expired and the refresh token was revoked; the connection was partially deleted leaving no access_token secret ref; an operator configured a managed connection expecting secret fallback that intentionally never happens (fails closed by design).","solutions":["Inspect the thrown message / `managed.error`: it carries the specific reason (unauthorized member, incomplete identity, no repository access, unresolvable personal credential) and fix that underlying condition.","Reconnect the managed GitHub connection: reinstall the GitHub App on the organization/repositories and re-authorize the OAuth grant so a fresh access token exists.","If the identity is a personal (user) connection, ensure the owner is an active company member with a non-viewer role and that their GitHub user secret is still present and resolvable.","If managed identity is not wanted, disconnect/remove the managed GitHub connection for the company so resolution falls through to company secrets or GITHUB_TOKEN/GH_TOKEN env credentials.","For self-hosted operators, as a non-managed fallback, set GITHUB_TOKEN or GH_TOKEN in the server process environment or store the token as a company secret with one of the well-known names."],"exampleFix":"// before: managed GitHub App uninstalled, resolution fails closed\nconst provider = createGitRemoteAuthProvider(db, companyId, { issueId });\nconst credential = await provider(remoteUrl); // throws 'Managed GitHub connection is unavailable'\n\n// after: operator reinstalls/re-authorizes the GitHub App (or disconnects it so env token applies)\n// env: GITHUB_TOKEN=ghp_...\nconst credential = await provider(remoteUrl); // returns { token, source: \"server_env\" }","handlingStrategy":"fallback","validationCode":"const managed = await resolveManagedGitHubCredential(db, secretService(db), companyId, { issueId });\nif (managed.configured && !managed.credential) {\n  console.error(\"Managed GitHub identity unusable:\", managed.error); // fix before any git op\n}","typeGuard":"function hasManagedCredential(m: { configured: boolean; credential?: unknown }): m is { configured: true; credential: NonNullable<unknown> } {\n  return m.configured && m.credential != null;\n}","tryCatchPattern":"try {\n  const credential = await provider(remoteUrl);\n} catch (err) {\n  if (err instanceof Error && err.message.includes(\"Managed GitHub connection\")) {\n    // alert operator to reconnect the GitHub App; do not silently fall back\n  }\n  throw err;\n}","preventionTips":["Monitor the managed connection's OAuth expiry and refresh health before runs start.","Alert when a GitHub App installation count or repository count drops to zero.","Keep identity owners as active non-viewer members; audit membership changes.","Remember resolution fails closed: never rely on secret/env fallback while a managed connection is configured."],"tags":["git","github","credentials","oauth","configuration"],"backgroundTag":"missing-credentials","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}