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.

Solutions

  1. Restore the grant owner's active company membership (or upgrade from viewer to member/admin)
  2. Re-authorize the GitHub grant under a different active member
  3. Delete/re-create the personal grant under an eligible member's identity
  4. 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

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


AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18). Data as JSON: /api/errors/4986871907f9d5bd. Report an issue: GitHub.

Appendix: source

Thrown at server/src/services/tool-gateway.ts:4352

            "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 })

View on GitHub (pinned to 3f1d897a7c)