mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-10 12:37:55 +01:00
fix(status-cards): remove a new card only when its compile cannot be queued
The create route's cleanup also wrapped the activity record written after the compile was queued, so a logging failure deleted the card and left its generation task queued. Only enqueueCompile failures now remove the card; a regression test fails the creation activity record and checks that the card and its uncancelled generation task remain. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
cfe3decc7c
commit
b3f8bbfdb9
2 files changed
+48
-5
No files matched your search
@@ -194,6 +194,47 @@ describeEmbeddedPostgres("status card routes", () => {
|
||||
expect(await db.select().from(issues)).toEqual([]);
|
||||
});
|
||||
|
||||
it("keeps a card with a queued compile when its activity record fails", async () => {
|
||||
const company = await seedCompany();
|
||||
await enableStatusCards();
|
||||
await seedSummarizer(company.id);
|
||||
// Fail only the card's creation activity record, which is written after the compile is queued.
|
||||
const failingActivityDb = new Proxy(db, {
|
||||
get(target, property) {
|
||||
if (property === "insert") {
|
||||
return (table: unknown) => {
|
||||
const builder = target.insert(table as never);
|
||||
if (table !== activityLog) return builder;
|
||||
return new Proxy(builder, {
|
||||
get(inner, key) {
|
||||
if (key === "values") {
|
||||
return (values: { action?: string }) => {
|
||||
if (values?.action === "status_card.created") throw new Error("activity log unavailable");
|
||||
return inner.values(values as never);
|
||||
};
|
||||
}
|
||||
const value = Reflect.get(inner, key, inner);
|
||||
return typeof value === "function" ? value.bind(inner) : value;
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
const value = Reflect.get(target, property, target);
|
||||
return typeof value === "function" ? value.bind(target) : value;
|
||||
},
|
||||
}) as Db;
|
||||
const response = await request(createApp(failingActivityDb, localBoardActor()))
|
||||
.post(`/api/companies/${company.id}/status-cards`)
|
||||
.send({ interestPrompt: "Recently updated launch tasks" });
|
||||
|
||||
expect(response.status).toBe(500);
|
||||
const cards = await db.select().from(statusCards);
|
||||
expect(cards).toHaveLength(1);
|
||||
const generation = await db.select().from(issues).where(eq(issues.id, cards[0]!.generatingIssueId!)).then((rows) => rows[0]);
|
||||
expect(generation).toBeDefined();
|
||||
expect(generation!.status).not.toBe("cancelled");
|
||||
});
|
||||
|
||||
it("creates, patches, archives, restores, lists updates, and deletes a card", async () => {
|
||||
const company = await seedCompany();
|
||||
await enableStatusCards();
|
||||
|
||||
@@ -172,21 +172,23 @@ export function statusCardRoutes(db: Db, opts: { heartbeat?: IssueAssignmentWake
|
||||
await assertCanMutate(req, companyId);
|
||||
assertAgentPromptLimit(req, req.body.interestPrompt);
|
||||
const actor = getActorInfo(req);
|
||||
// Resolve the link before creating the card: the catch below deletes the card, so nothing that
|
||||
// runs after the compile is queued may fail there.
|
||||
// Resolve the link before creating the card, so a failed lookup leaves nothing behind.
|
||||
const issuePrefix = await companyIssuePrefix(companyId);
|
||||
const card = await service.create(companyId, req.body, {
|
||||
agentId: actor.actorType === "agent" ? actor.actorId : null,
|
||||
userId: actor.actorType === "user" ? actor.actorId : null,
|
||||
});
|
||||
// Only a failure to queue the compile removes the new card. Once the compile is queued, the card
|
||||
// and its generation task exist together, so later failures must not delete the card alone.
|
||||
let compile: Awaited<ReturnType<typeof enqueueCompile>>;
|
||||
try {
|
||||
const compile = await enqueueCompile(req, card.id);
|
||||
await logMutation(req, companyId, "status_card.created", card.id, { state: card.state });
|
||||
res.status(201).json(withHref(publicCard(compile.card), issuePrefix));
|
||||
compile = await enqueueCompile(req, card.id);
|
||||
} catch (error) {
|
||||
await service.remove(card.id);
|
||||
throw error;
|
||||
}
|
||||
await logMutation(req, companyId, "status_card.created", card.id, { state: card.state });
|
||||
res.status(201).json(withHref(publicCard(compile.card), issuePrefix));
|
||||
});
|
||||
|
||||
router.get("/status-cards/:id", async (req, res) => {
|
||||
|
||||
Reference in new issue
Block a user