Skip to content

Commit 3eeed1b

Browse files
fix(kiloclaw): scope subscription selection to personal instances for billing mutations (#2189)
1 parent 6aa4e3b commit 3eeed1b

File tree

5 files changed

+776
-65
lines changed

5 files changed

+776
-65
lines changed

apps/web/src/lib/kiloclaw/access-gate.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ export async function requireKiloClawAccess(userId: string): Promise<void> {
1717
const now = new Date();
1818

1919
// 1. Active subscription
20-
const { accessReason } = await getEffectiveKiloClawSubscriptionForUser(userId, now);
20+
const { subscription, accessReason, subscriptionCount } =
21+
await getEffectiveKiloClawSubscriptionForUser(userId, now);
2122
if (accessReason) {
2223
return;
2324
}
@@ -33,6 +34,25 @@ export async function requireKiloClawAccess(userId: string): Promise<void> {
3334
return;
3435
}
3536

37+
console.warn(
38+
JSON.stringify({
39+
event: 'kiloclaw_access_denied',
40+
userId,
41+
subscriptionCount,
42+
effectiveSubscription: subscription
43+
? {
44+
id: subscription.id,
45+
status: subscription.status,
46+
plan: subscription.plan,
47+
suspended_at: subscription.suspended_at,
48+
instance_id: subscription.instance_id,
49+
}
50+
: 'none',
51+
accessReason,
52+
earlybirdFound: !!earlybird,
53+
})
54+
);
55+
3656
throw new TRPCError({
3757
code: 'FORBIDDEN',
3858
message: 'KiloClaw access requires an active subscription, trial, or earlybird purchase.',

apps/web/src/lib/kiloclaw/access-state.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ export async function getEffectiveKiloClawSubscriptionForUser(
7171
): Promise<{
7272
subscription: KiloClawSubscription | null;
7373
accessReason: KiloClawAccessReason | null;
74+
subscriptionCount: number;
7475
}> {
7576
const subscriptions = await db
7677
.select()
@@ -81,5 +82,6 @@ export async function getEffectiveKiloClawSubscriptionForUser(
8182
return {
8283
subscription,
8384
accessReason: getKiloClawSubscriptionAccessReason(subscription, now),
85+
subscriptionCount: subscriptions.length,
8486
};
8587
}

apps/web/src/lib/kiloclaw/stripe-handlers.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -442,12 +442,20 @@ export async function handleKiloClawSubscriptionCreated(params: {
442442
let convertedFromTrial = false;
443443

444444
await db.transaction(async tx => {
445-
// Look up the user's active instance to link the subscription.
445+
// Look up the user's active personal instance to link the subscription.
446+
// Scoped to personal instances (organization_id IS NULL) so a checkout
447+
// from the /claw page never attaches to an org-owned instance. If no
448+
// personal instance exists the subscription is inserted with
449+
// instance_id = NULL and adoptOrphanedSubscription handles it later.
446450
const [activeInstance] = await tx
447451
.select({ id: kiloclaw_instances.id })
448452
.from(kiloclaw_instances)
449453
.where(
450-
and(eq(kiloclaw_instances.user_id, kiloUserId), isNull(kiloclaw_instances.destroyed_at))
454+
and(
455+
eq(kiloclaw_instances.user_id, kiloUserId),
456+
isNull(kiloclaw_instances.destroyed_at),
457+
isNull(kiloclaw_instances.organization_id)
458+
)
451459
)
452460
.limit(1);
453461

0 commit comments

Comments
 (0)