paperclipai/paperclip · error · ToolGatewayHttpError
grant_owner_membership_inactive
grant_owner_membership_inactive
Error message
The personal grant owner is not an authorized company member
What it means
Thrown when a personal (user-kind) GitHub grant is selected but its owner is not an active company member, or their membership role is 'viewer'. Viewers and non-members may not lend their personal GitHub identity to company tool calls, so a 403 is raised.
Source
Thrown at server/src/services/tool-gateway.ts:3267
if (session.identityContextId && session.agentId && (
connection.config.sourceTemplateKey === "github" || connection.transportConfig?.sourceTemplateKey === "github"
)) {
const captured = githubOperationCredentials.get(session);
const selected = captured
? { grant: captured.grant, error: undefined }
: await resolveManagedGitHubIdentitySelection(db, session.companyId, {
agentId: session.agentId, responsibleUserId: session.responsibleUserId, allowStandingDelegation: false,
});
if (!selected.grant || selected.grant.connectionId !== connection.id) {
throw new ToolGatewayHttpError(409, selected.error ?? "GitHub identity changed; retry through the managed tool", "github_identity_unavailable");
}
if (selected.grant.kind === "user") {
const [member] = await db.select({ role: companyMemberships.membershipRole }).from(companyMemberships).where(and(
eq(companyMemberships.companyId, session.companyId), eq(companyMemberships.principalType, "user"),
eq(companyMemberships.principalId, selected.grant.subjectUserId!), eq(companyMemberships.status, "active"),
)).limit(1);
if (!member || member.role === "viewer") {
throw new ToolGatewayHttpError(403, "The personal grant owner is not an authorized company member", "grant_owner_membership_inactive");
}
}
// Managed GitHub selection is final: a legacy shared policy cannot replace
// the captured person's grant with an organization or teammate's account.
return selected.grant;
}
const autonomous = !session.identityContextId && (run?.invocationSource === "automation" || run?.invocationSource === "timer");
const findUserGrant = async () => {
if (!actingUserId) return undefined;
const [membership] = await db.select({ id: companyMemberships.id }).from(companyMemberships).where(and(
eq(companyMemberships.companyId, connection.companyId),
eq(companyMemberships.principalType, "user"),
eq(companyMemberships.principalId, actingUserId),
eq(companyMemberships.status, "active"),
)).limit(1);
if (!membership) {
throw new ToolGatewayHttpError(403, "The personal grant owner is not an active company member", "grant_owner_membership_inactive", {
connectionId: connection.id,View on GitHub (pinned to 01ad858492)
Solutions
- Restore the grant owner's active company membership (or upgrade from viewer to member/admin)
- Re-authorize the GitHub grant under a different active member
- Delete/re-create the personal grant under an eligible member's identity
- Audit grants whose owners have left and clean them up
Example fix
// before
// grant owner role: viewer -> 403
await callGitHubTool(session, connId, p);
// after
await db.update(companyMemberships).set({ membershipRole: "member" })
.where(eq(companyMemberships.principalId, grantOwnerId));
await callGitHubTool(session, connId, p); Defensive patterns
Strategy: validation
Validate before calling
const [m] = await db.select().from(companyMemberships).where(and(
eq(companyMemberships.companyId, companyId),
eq(companyMemberships.principalId, grantOwnerUserId),
eq(companyMemberships.status, "active")));
if (!m || m.membershipRole === "viewer") throw new Error("Grant owner not eligible; reassign grant"); Type guard
function ownerIsEligibleMember(m?: { membershipRole: string } | null): boolean {
return !!m && m.membershipRole !== "viewer";
} Try / catch
try { await callGitHubTool(session, connId, p); }
catch (e) {
if (e.code === "grant_owner_membership_inactive") await reassignGrantToActiveMember(connId);
else throw e;
} Prevention
- Reassign or delete personal grants when the owner's membership changes
- Block owner deactivation/downgrade while grants they own are in use
- Audit user-kind grants against active memberships on a schedule
- Prefer org/shared grants over personal ones where possible
When it happens
Trigger: Managed GitHub identity resolution selects a user-kind grant whose subjectUserId has no active companyMemberships row for the session's company, or the row has membershipRole='viewer'.
Common situations: Grant owner left the company or their membership was deactivated after authorizing the GitHub grant; owner downgraded to viewer role; membership row deleted while the grant persisted; agent work reassigned to a viewer.
Understand the failure class
Background: "You do not have permission" / 403 Forbidden errors: authenticated but not allowed — causes and fixes across open-source libraries — this error's family across 31 libraries.
Related errors
- Could not prepare managed GitHub launchers
- Discord command registration authorization denied
- GitHub inventory failed: the active installation has not gra
- github_identity_unavailable
- Registered base project workspace Paperclip config at ${conf
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/4986871907f9d5bd.
Report an issue: GitHub.