fix(agent-jobs): validate claims before claiming

This commit is contained in:
Gabriel Brown
2026-07-10 17:26:17 -04:00
parent 926008fd3a
commit cf45404b7d
3 changed files with 281 additions and 17 deletions
+60 -2
View File
@@ -1074,6 +1074,65 @@ export const claimNextInternal = internalMutation({
secrets.push(secret);
}
}
const ownedProfile =
aiProviderProfile?.ownerId === job.ownerId && aiProviderProfile.enabled
? aiProviderProfile
: null;
const failClaim = async (reason: string) => {
const failedAt = Date.now();
await ctx.db.patch(job._id, {
status: 'failed',
error: reason,
completedAt: failedAt,
updatedAt: failedAt,
});
await ctx.db.patch(job.agentRequestId, {
status: 'failed',
updatedAt: failedAt,
});
if (job.threadId) {
await ctx.db.patch(job.threadId, {
status: 'failed',
updatedAt: failedAt,
resolvedAt: failedAt,
});
}
await ctx.db.insert('agentJobEvents', {
jobId: job._id,
spoonId: job.spoonId,
ownerId: job.ownerId,
level: 'error',
phase: 'queued',
message: `Job could not be claimed: ${reason}`,
createdAt: failedAt,
});
};
if (!spoon) {
await failClaim('The Spoon for this job no longer exists.');
return null;
}
if (!ownedProfile) {
await failClaim(
'AI is not configured for this user. Add an AI provider in settings.',
);
return null;
}
if (ownedProfile.authType !== 'none' && !ownedProfile.encryptedSecret) {
await failClaim('Selected AI provider is missing credentials.');
return null;
}
if (agentSettings && !agentSettings.enabled) {
await failClaim('Agent jobs are disabled for this Spoon.');
return null;
}
if ((job.runtime ?? 'opencode') !== 'opencode') {
await failClaim('Legacy OpenAI direct jobs are no longer supported.');
return null;
}
if (secrets.length !== job.selectedSecretIds.length) {
await failClaim('Selected secrets must belong to this Spoon.');
return null;
}
const now = Date.now();
await ctx.db.patch(job._id, {
status: 'claimed',
@@ -1098,8 +1157,7 @@ export const claimNextInternal = internalMutation({
job: { ...job, status: 'claimed' as const, claimedBy: workerId },
spoon,
aiSettings,
aiProviderProfile:
aiProviderProfile?.ownerId === job.ownerId ? aiProviderProfile : null,
aiProviderProfile: ownedProfile,
agentSettings,
secrets,
};