{"record":{"id":"1321c09909a07f36","repo":"mastra-ai/mastra","slug":"github-token-refresh-no-longer-matches-the-active","errorCode":null,"errorMessage":"GitHub token refresh no longer matches the active Factory workspace role.","messagePattern":"GitHub token refresh no longer matches the active Factory workspace role\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"mastracode/factory/src/workspace.ts","lineNumber":435,"sourceCode":"    };\n    const resolveGithubPatKind = async (fallback: GithubPatKind): Promise<GithubPatKind> => {\n      if (!workItems) return 'default';\n      try {\n        const address = getFactorySessionAddress(requestContext);\n        const runBinding = address ? await workItems.findRunBindingBySession(address) : null;\n        return runBinding?.role === 'review' && runBinding.status === 'active' && runBinding.orgId === session.orgId\n          ? 'reviewer'\n          : 'default';\n      } catch {\n        // Preserve the installed role when binding storage is temporarily unavailable.\n        return fallback;\n      }\n    };\n    const registerGithubTokenContext = (registered: GithubTokenRegistration): void => {\n      const generation = registered.generation;\n      registerGithubTokenInjector(requestContext, token => {\n        if (githubTokenInjectors.get(workspaceId) !== registered || registered.generation !== generation) {\n          throw new Error('GitHub token refresh no longer matches the active Factory workspace role.');\n        }\n        registered.inject(token);\n      });\n      registerGithubPatKind(requestContext, registered.patKind);\n    };\n    const reconcileGithubToken = async (): Promise<void> => {\n      const previous = githubTokenReconciliations.get(workspaceId) ?? Promise.resolve();\n      const reconciliation = previous\n        .catch(() => {})\n        .then(async () => {\n          const registered = githubTokenInjectors.get(workspaceId);\n          if (!registered) return;\n\n          const previousPatKind = registered.patKind;\n          const patKind = await resolveGithubPatKind(previousPatKind);\n          if (githubTokenInjectors.get(workspaceId) !== registered) return;\n\n          if (patKind !== previousPatKind) {","sourceCodeStart":417,"sourceCodeEnd":453,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/mastracode/factory/src/workspace.ts#L417-L453","documentation":"When a GitHub token is registered for a Factory workspace, the injector closure is bound to both the registration object and a generation counter. If the injector later fires but the registration was replaced (a new generation was registered) or removed, the error is thrown to prevent injecting a token from a stale role into the current workspace context.","triggerScenarios":"A token refresh callback runs after `registerGithubTokenInjector` was superseded: `githubTokenInjectors.get(workspaceId)` no longer equals `registered`, or `registered.generation` differs from the captured `generation`. Raised via registerGithubTokenContext during setupHook or reconciliation.","commonSituations":"Concurrent workspace reconciliation re-registering credentials while an old refresh is in flight; workspace role switched (e.g., different PAT kind or user impersonation) mid-session; race between session teardown and a pending token refresh.","solutions":["Retry the operation — reconciliation will re-register a fresh token context and the next refresh will match","Check for concurrent setup/reconciliation paths racing on the same workspaceId and serialize them","Restart or re-materialize the workspace so token registration and role are consistent","Upgrade: if this fires repeatedly without role changes, it indicates a generation-tracking bug — capture diagnostics"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// before relying on the injector, confirm the current registration is still active\nconst current = githubTokenInjectors.get(workspaceId);\nif (current !== registered || registered.generation !== generation) {\n  await reRegisterGithubTokenContext(); // refresh instead of injecting stale token\n}","typeGuard":null,"tryCatchPattern":"try {\n  await runWithGithubToken(requestContext, token => registered.inject(token));\n} catch (e) {\n  if (e instanceof Error && e.message.includes('no longer matches the active Factory workspace role')) {\n    await reconcileGithubToken(); // retry with fresh registration\n  } else throw e;\n}","preventionTips":["Serialize workspace reconciliation per workspaceId to avoid generation races","Avoid holding token injector references across role switches","Re-read the registration after any role/impersonation change","Log generation changes to diagnose recurring staleness"],"tags":["race-condition","github","auth","stale-state","factory"],"backgroundTag":"stale-credential-context","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}