paperclipai/paperclip · error · Error
Project tool authentication is unavailable
Error message
Project tool authentication is unavailable
What it means
The project tools (create_project, list_project_repositories, list_projects) call back into the Paperclip API using binding.apiUrl ?? PAPERCLIP_API_URL and a locally minted agent JWT. This error means the API URL or token could not be produced, so the project tool cannot make an authenticated request.
Solutions
- Set PAPERCLIP_API_URL in the runner environment or pass binding.apiUrl.
- Ensure the run/actor context includes responsibleUserId and adapterType so createLocalAgentJwt succeeds.
- Confirm binding.agentId and binding.companyId are set when constructing the tool authority.
- In containerized runners, propagate the Paperclip API env/config at launch time.
Example fix
// before
const authority = new RunnerToolAuthority({ db, binding: { agentId, issueId, runId } }); // no apiUrl
// after
const authority = new RunnerToolAuthority({ db, binding: { agentId, issueId, runId, apiUrl: process.env.PAPERCLIP_API_URL } }); Defensive patterns
Strategy: validation
Validate before calling
function assertProjectToolAuth(binding) {
const apiUrl = binding.apiUrl ?? process.env.PAPERCLIP_API_URL;
if (!apiUrl) throw new Error("PAPERCLIP_API_URL must be set for project tools");
return apiUrl;
} Type guard
const hasProjectAuth = (binding) => Boolean(binding.apiUrl ?? process.env.PAPERCLIP_API_URL);
Try / catch
try {
return await authority.execute(call);
} catch (e) {
if (e.message === "Project tool authentication is unavailable") {
return respondSkipped("Project tools require PAPERCLIP_API_URL; fix runner config and retry.");
}
throw e;
} Prevention
- Propagate PAPERCLIP_API_URL into runner containers/CLI launches.
- Set binding.apiUrl at authority construction instead of depending on process env.
- Verify createLocalAgentJwt inputs (agentId, companyId, adapterType, runId, responsibleUserId) before enabling project tools.
- Add a startup probe that mints a test JWT to catch config drift early.
When it happens
Trigger: execute() routes call.tool to the create_project/list_project_repositories/list_projects case and either apiUrl (binding.apiUrl ?? process.env.PAPERCLIP_API_URL) is falsy or createLocalAgentJwt(this.binding.agentId, this.binding.companyId, context.actor.adapterType, this.binding.runId, context.run.responsibleUserId) returns an empty token.
Common situations: Runner process started without PAPERCLIP_API_URL (local CLI, container missing env); binding constructed without apiUrl; run row missing responsibleUserId so token creation fails; agent/agentId context incomplete in test harnesses.
Understand the failure class
Background: "environment variable is not set" and "Missing keys in environment" errors: what missing required env var messages mean and how to fix them — this error's family across 28 libraries.
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Review tool authentication is unavailable
- Skill tool authentication is unavailable
- Agent identity is required
- ANTHROPIC_API_KEY is required in the CLI process environment
- Cloud control assertion is not a compact JWS
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/f5340dace10e3a63.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/native-runtime/paperclip-runner-tool-authority.ts:357
!descriptor.allowedModes.includes(
context.issue.workMode as "standard" | "planning" | "ask",
)
) {
throw new Error("paperclip_runner_tool_mode_denied");
}
switch (call.tool) {
case "create_skill": {
const apiUrl = this.binding.apiUrl ?? process.env.PAPERCLIP_API_URL;
const token = createLocalAgentJwt(this.binding.agentId, this.binding.companyId, context.actor.adapterType, this.binding.runId, context.run.responsibleUserId);
if (!apiUrl || !token) throw new Error("Skill tool authentication is unavailable");
return callCreateSkillTool({ arguments: input, apiUrl, token, companyId: this.binding.companyId });
}
case "create_project":
case "list_project_repositories":
case "list_projects": {
const apiUrl = this.binding.apiUrl ?? process.env.PAPERCLIP_API_URL;
const token = createLocalAgentJwt(this.binding.agentId, this.binding.companyId, context.actor.adapterType, this.binding.runId, context.run.responsibleUserId);
if (!apiUrl || !token) throw new Error("Project tool authentication is unavailable");
return callProjectTool({ name: call.tool, arguments: input, apiUrl, token,
companyId: this.binding.companyId, issueId: this.binding.issueId, agentId: this.binding.agentId,
conversation: Boolean(context.issue.conversationAgentId) });
}
case "search_api": return searchRunnerApi(call.arguments);
case "call_api": {
// PRP reserves operationId/callId for semantic result identity. The
// HTTP operation is metadata, including in previously saved receipts;
// exposing it as operationId makes the runner reject a valid response.
const { operationId, ...response } = record(await this.#callApi(call.callId, call.arguments));
return { ...response, apiOperationId: operationId };
}
case "get_task_context": return {
company: { id: this.binding.companyId },
actor: redactedActor(context.actor),
activeTask: redactedTask(context.issue),
run: {
id: this.binding.runId,View on GitHub (pinned to 3f1d897a7c)