paperclipai/paperclip · error
Run telemetry is outside this actor's authorization boundary
Error message
Run telemetry is outside this actor's authorization boundary
What it means
This is an HTTP 403 JSON error emitted by `assertRunTelemetryReadAllowed` in the agents router. Before serving run telemetry (per-run events, heartbeat run logs) for a company, the route asks the access-control service whether the actor may perform `company_scope:read` on that company. If the decision is denied, the endpoint responds with this message instead of the data, enforcing that agent API keys (and other limited actors) cannot read telemetry outside their own company authorization boundary.
Source
Thrown at server/src/routes/agents.ts:1075
resource: { type: "agent", companyId: agent.companyId, agentId: agent.id },
});
}
async function assertAgentReadAllowed(req: Request, res: Response, agent: { id: string; companyId: string }) {
const decision = await decideAgentRead(req, agent);
if (decision.allowed) return true;
res.status(403).json({ error: "Agent is outside this actor's authorization boundary" });
return false;
}
async function assertRunTelemetryReadAllowed(req: Request, res: Response, companyId: string) {
const decision = await access.decide({
actor: req.actor,
action: "company_scope:read",
resource: { type: "company", companyId },
});
if (decision.allowed) return true;
res.status(403).json({ error: "Run telemetry is outside this actor's authorization boundary" });
return false;
}
async function filterAgentsForActor<T extends Record<string, unknown>>(
req: Request,
rows: T[],
fallbackCompanyId?: string,
) {
const decisions = await Promise.all(rows.map((agent) => {
const id = typeof agent.id === "string" ? agent.id : null;
const companyId = typeof agent.companyId === "string" ? agent.companyId : fallbackCompanyId ?? null;
if (!id || !companyId) return Promise.resolve({ allowed: false });
return decideAgentRead(req, { id, companyId });
}));
return rows.filter((_, index) => decisions[index]?.allowed);
}
/**View on GitHub (pinned to 01ad858492)
Solutions
- Verify the actor's key actually belongs to the same company as the telemetry being requested; correct the companyId in the request.
- Use a board/operator actor (full-control context) for cross-company or admin telemetry reads.
- Issue a new agent API key with the appropriate scope if the agent legitimately needs company-scope read.
- Check the client config/env for the wrong base company selection (e.g., pointing at another workspace's company id).
- Re-authenticate if the key was rotated or the agent's company assignment changed.
Example fix
// before: agent key hitting telemetry of another company
const res = await fetch(`/api/companies/${otherCompanyId}/runs/${runId}/events`, { headers: agentAuth });
// after: use the actor's own company, or board auth for cross-company reads
const res = await fetch(`/api/companies/${myCompanyId}/runs/${runId}/events`, { headers: boardAuth }); Defensive patterns
Strategy: validation
Validate before calling
// client-side check before calling telemetry endpoints
const companyId = resolveActorCompanyId(actor);
if (requestedCompanyId !== companyId && actor.type !== 'board') {
throw new Error('actor cannot read telemetry outside its own company');
} Type guard
function canReadCompanyScope(actor: Actor, companyId: string): boolean {
return actor.type === 'board' || (actor.type === 'agent' && actor.companyId === companyId);
} Try / catch
const res = await fetch(telemetryUrl, { headers: auth });
if (res.status === 403) {
const body = await res.json();
if (body.error.includes('authorization boundary')) {
throw new ForbiddenError('telemetry outside actor boundary; use board auth or own company id');
}
} Prevention
- Always derive companyId from the authenticated actor, never from untrusted client input.
- Use board/operator credentials for cross-company telemetry reads.
- Audit agent key scopes when moving agents between companies.
- Centralize telemetry URL construction so companyId comes from one validated source.
When it happens
Trigger: A client calls a run-telemetry endpoint (run events / heartbeat run log routes that route through assertRunTelemetryReadAllowed) with an actor whose access.decide() for action `company_scope:read` on the target companyId returns { allowed: false } — typically an agent bearer key scoped to a different company or restricted keyScope.
Common situations: Using an agent_api_keys bearer token to fetch telemetry for another company's runs; a key scoped to a task bridge or limited scope that lacks company_scope:read; passing the wrong companyId in the URL after switching workspaces; stale key after the agent was moved between companies.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
Related errors
- trustPreset.detail
- Runtime service control is outside this actor's authorizatio
- Plugin UI is not available (status: ${plugin.status})
- Access denied
- dropping 1 event whose serialized envelope exceeds maxBodyBy
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/cf7aa990abd851f7.
Report an issue: GitHub.