{"record":{"id":"f078e4f4dfba0e27","repo":"paperclipai/paperclip","slug":"local-stdio-missing-secret","errorCode":"local_stdio_missing_secret","errorMessage":"A configured local stdio credential could not be resolved.","messagePattern":"A configured local stdio credential could not be resolved\\.","errorType":"http","errorClass":"ToolGatewayHttpError","httpStatus":422,"severity":"error","filePath":"server/src/services/tool-gateway.ts","lineNumber":4862,"sourceCode":"    for (const key of template.envKeys) {\n      const grantRef = grant.credentialSecretRefs.find(\n        (ref) => ref.configPath === `env.${key}`,\n      );\n      if (!grantRef) continue;\n      try {\n        env[key] = await resolveGrantSecretValue(\n          session,\n          connection,\n          grant,\n          grantRef,\n        );\n      } catch {\n        await markRemoteConnectionHealth(\n          connection,\n          \"missing_secret\",\n          \"A configured local stdio credential could not be resolved.\",\n        );\n        throw new ToolGatewayHttpError(\n          422,\n          \"A configured local stdio credential could not be resolved.\",\n          \"local_stdio_missing_secret\",\n          { connectionId: connection.id, credential: grantRef.configPath },\n        );\n      }\n    }\n    return env;\n  }\n\n  function stdioProtocolError(\n    message: string,\n    details: Record<string, unknown> = {},\n  ) {\n    return new ToolGatewayHttpError(\n      502,\n      message,\n      \"local_stdio_protocol_error\",","sourceCodeStart":4844,"sourceCodeEnd":4880,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/server/src/services/tool-gateway.ts#L4844-L4880","documentation":"This ToolGatewayHttpError (HTTP 422) is thrown when a local stdio MCP connection references a credential (grantRef.configPath) that cannot be resolved from the local environment/secrets store; the resolution attempt threw and the catch block first marks the connection health as 'missing_secret', then throws. The connection is unusable until the referenced secret exists where the server can read it.","triggerScenarios":"Calling a tool through a connection with transport 'local_stdio' whose runtime template includes a credential reference (configPath) that fails resolution — e.g. the referenced env secret is absent, the secrets store entry was deleted, or the credential file/key is unreadable so the resolve call throws.","commonSituations":"Running the server on a new machine or CI runner where the .env / secret file backing the stdio credential was never copied; rotating credentials in the secrets store without updating the connection config; a typo'd or stale configPath after a migration; exported connection configs referencing machine-local secrets that don't exist elsewhere.","solutions":["Create/restore the missing secret at the referenced location so grantRef.configPath resolves (set the env var or add the entry to the local secrets store), then retry.","Update the connection's local stdio config to point credential configPath at a secret that actually exists on this host.","Inspect the connection health status (markRemoteConnectionHealth set it to 'missing_secret') and re-test the connection after fixing the credential.","If the credential was rotated, re-authorize or re-create the connection so its credential references match the current store keys."],"exampleFix":"// before: connection config references a secret that is absent\n{ \"transport\": \"local_stdio\", \"credential\": { \"configPath\": \"env.GITHUB_TOKEN\" } } // GITHUB_TOKEN unset\n// after: provide it where the server runs\nexport GITHUB_TOKEN=$(op read \"op://Vault/github/token\") # or add to the server's .env","handlingStrategy":"validation","validationCode":"const secret = process.env[grantRef.configPath.replace(/^env\\./, \"\")];\nif (!secret || secret.trim() === \"\") {\n  throw new Error(`Local stdio credential ${grantRef.configPath} is not set for connection ${connection.id}.`);\n}","typeGuard":"function hasResolvableCredential(grantRef: { configPath: string }, env: Record<string, string | undefined>): boolean {\n  const key = grantRef.configPath.startsWith(\"env.\") ? grantRef.configPath.slice(4) : grantRef.configPath;\n  return Boolean(env[key] && env[key]!.length > 0);\n}","tryCatchPattern":"try {\n  await callLocalStdioTool(session, connectionId, toolName, args);\n} catch (e) {\n  if (e instanceof ToolGatewayHttpError && e.code === \"local_stdio_missing_secret\") {\n    return { status: \"missing_secret\", credential: e.details.credential, remediation: `Set ${e.details.credential} on the server host` };\n  }\n  throw e;\n}","preventionTips":["Add a startup health check that resolves every local stdio connection's credentials and flags 'missing_secret' before agents run.","Provision secrets on new hosts/CI runners as part of environment setup (documented .env or secret-store bootstrap).","When rotating credentials, update both the store and any connection configPaths that reference them.","Treat connection health status 'missing_secret' as blocking and surface it before scheduling agent work."],"tags":["secrets","env","mcp","stdio","config"],"backgroundTag":"missing-env-var","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-22T06:17:15.046Z"}