{"record":{"id":"09a11e0c27692a35","repo":"danny-avila/LibreChat","slug":"github-skill-sync-runner-is-not-configured","errorCode":null,"errorMessage":"GitHub skill sync runner is not configured","messagePattern":"GitHub skill sync runner is not configured","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/api/src/admin/skills.ts","lineNumber":362,"sourceCode":"    }\n  };\n\n  return {\n    attachBaseSkillSyncConfig,\n    attachCredentialReadAccess,\n    requireReadSkills: requireSkillCapability(SystemCapabilities.READ_SKILLS),\n    requirePlatformManageSkills: requireSkillCapability(SystemCapabilities.MANAGE_SKILLS, {\n      platformOnly: true,\n    }),\n    requireSyncRunCapability,\n  };\n}\n\nexport function createAdminSkillsSyncHandlers(deps: AdminSkillSyncDeps): AdminSkillsSyncHandlers {\n  function getRunner(req: Request): GitHubSkillSyncRunner {\n    const runner = deps.getRunner?.(req) ?? deps.runner;\n    if (!runner) {\n      throw new Error('GitHub skill sync runner is not configured');\n    }\n    return runner;\n  }\n\n  async function getSyncStatus(req: AdminSkillsRequest, res: Response) {\n    const includeCredentialMetadata = req.skillSyncCanReadCredentials !== false;\n    const status = await getRunner(req).getStatus();\n    const response: TGitHubSkillSyncStatusResponse = {\n      enabled: status.enabled,\n      intervalMinutes: status.intervalMinutes,\n      runOnStartup: status.runOnStartup,\n      sources: getVisibleSourceStatuses(req, status.sources).map((source) =>\n        serializeSourceStatus(source, { includeCredentialMetadata }),\n      ),\n      credentials: includeCredentialMetadata ? status.credentials.map(serializeCredential) : [],\n      fineGrainedTokenRecommendation: status.fineGrainedTokenRecommendation,\n    };\n    return res.status(200).json(response);","sourceCodeStart":344,"sourceCodeEnd":380,"githubUrl":"https://github.com/danny-avila/LibreChat/blob/5ff282f9006c436e561de1afd39a481bea1ef0d8/packages/api/src/admin/skills.ts#L344-L380","documentation":"createAdminSkillsSyncHandlers builds a getRunner helper that resolves the GitHubSkillSyncRunner from deps.getRunner(req) or deps.runner. If neither is supplied when the handlers were constructed, every sync endpoint (status, run, setCredential, deleteCredential) throws this error on the first call. The runner is the component that actually performs GitHub skill repository sync.","triggerScenarios":"Calling any admin skill-sync endpoint when the server was started without wiring a GitHubSkillSyncRunner into createAdminSkillsSyncHandlers' deps; feature flag for skill sync disabled but routes still mounted; misconfigured DI where getRunner returns undefined for the current tenant.","commonSituations":"Skill-sync feature turned off by config but routes exposed; a tenant/multi-tenant deployment where the runner factory returns undefined for some tenants; deploying without setting the GitHub skill sync env vars (token, sources).","solutions":["Provide a runner (or a getRunner factory) when calling createAdminSkillsSyncHandlers.","If the feature is intentionally disabled, do not mount the sync routes — return 501/503 from the router instead of letting handlers throw.","For multi-tenant setups, ensure getRunner returns a runner for every tenant or returns a clear 404."],"exampleFix":"// before\nconst handlers = createAdminSkillsSyncHandlers({} as AdminSkillSyncDeps); // no runner\n\n// after\nconst runner = createGitHubSkillSyncRunner({ sources, tokenStore });\nconst handlers = createAdminSkillsSyncHandlers({ runner });","handlingStrategy":"validation","validationCode":"// before constructing handlers, confirm a runner is available\nfunction ensureRunner(deps: AdminSkillSyncDeps): void {\n  if (!deps.runner && typeof deps.getRunner !== 'function') {\n    throw new Error('Cannot mount skill sync routes without a GitHubSkillSyncRunner');\n  }\n}","typeGuard":"const hasRunner = (d: AdminSkillSyncDeps): boolean =>\n  Boolean(d.runner) || typeof d.getRunner === 'function';","tryCatchPattern":"router.get('/sync/status', async (req, res) => {\n  try {\n    await handlers.getSyncStatus(req, res);\n  } catch (error) {\n    if (error instanceof Error && error.message.includes('not configured')) {\n      return res.status(503).json({ error: 'Skill sync is not configured' });\n    }\n    throw error;\n  }\n});","preventionTips":["If the feature is disabled, do not mount the routes — return 501/503 from the router.","Provide either runner or getRunner whenever you call createAdminSkillsSyncHandlers.","For multi-tenant deployments, ensure getRunner covers every tenant."],"tags":["admin","skills","github","config","dependency-injection"],"backgroundTag":null,"analyzedSha":"5ff282f9006c436e561de1afd39a481bea1ef0d8","analyzedAt":"2026-08-12T21:38:08.145Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}