fix: address greptile feedback for blocker dependencies

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
dotta
2026-04-06 08:29:23 -05:00
parent 467f3a749a
commit 4c01a45d2a
5 changed files with 14 additions and 2 deletions
@@ -10,6 +10,7 @@ CREATE TABLE "issue_relations" (
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
ALTER TABLE "issue_relations" ADD CONSTRAINT "issue_relations_type_check" CHECK ("type" IN ('blocks'));--> statement-breakpoint
ALTER TABLE "issue_relations" ADD CONSTRAINT "issue_relations_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "issue_relations" ADD CONSTRAINT "issue_relations_issue_id_issues_id_fk" FOREIGN KEY ("issue_id") REFERENCES "public"."issues"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "issue_relations" ADD CONSTRAINT "issue_relations_related_issue_id_issues_id_fk" FOREIGN KEY ("related_issue_id") REFERENCES "public"."issues"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
@@ -17,4 +18,4 @@ ALTER TABLE "issue_relations" ADD CONSTRAINT "issue_relations_created_by_agent_i
CREATE INDEX "issue_relations_company_issue_idx" ON "issue_relations" USING btree ("company_id","issue_id");--> statement-breakpoint
CREATE INDEX "issue_relations_company_related_issue_idx" ON "issue_relations" USING btree ("company_id","related_issue_id");--> statement-breakpoint
CREATE INDEX "issue_relations_company_type_idx" ON "issue_relations" USING btree ("company_id","type");--> statement-breakpoint
CREATE UNIQUE INDEX "issue_relations_company_edge_uq" ON "issue_relations" USING btree ("company_id","issue_id","related_issue_id","type");
CREATE UNIQUE INDEX "issue_relations_company_edge_uq" ON "issue_relations" USING btree ("company_id","issue_id","related_issue_id","type");
+1 -1
View File
@@ -10,7 +10,7 @@ export const issueRelations = pgTable(
companyId: uuid("company_id").notNull().references(() => companies.id),
issueId: uuid("issue_id").notNull().references(() => issues.id, { onDelete: "cascade" }),
relatedIssueId: uuid("related_issue_id").notNull().references(() => issues.id, { onDelete: "cascade" }),
type: text("type").notNull(),
type: text("type").$type<"blocks">().notNull(),
createdByAgentId: uuid("created_by_agent_id").references(() => agents.id, { onDelete: "set null" }),
createdByUserId: text("created_by_user_id"),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
+7
View File
@@ -819,6 +819,13 @@ export function issueService(db: Db) {
}
if (deduped.length > 0) {
const lockedIssueIds = [issueId, ...deduped].sort();
await dbOrTx.execute(
sql`SELECT ${issues.id} FROM ${issues}
WHERE ${and(eq(issues.companyId, companyId), inArray(issues.id, lockedIssueIds))}
ORDER BY ${issues.id}
FOR UPDATE`,
);
const relatedIssues = await dbOrTx
.select({ id: issues.id })
.from(issues)
+2
View File
@@ -128,6 +128,8 @@ Paperclip fires automatic wakes in two scenarios:
1. **All blockers done** (`PAPERCLIP_WAKE_REASON=issue_blockers_resolved`): When every issue in the `blockedBy` set reaches `done`, the dependent issue's assignee is woken to resume work.
2. **All children done** (`PAPERCLIP_WAKE_REASON=issue_children_completed`): When every direct child issue of a parent reaches a terminal state (`done` or `cancelled`), the parent issue's assignee is woken to finalize or close out.
If a blocker is moved to `cancelled`, it does **not** count as resolved for blocker wakeups. Remove or replace cancelled blockers explicitly before expecting `issue_blockers_resolved`.
When you receive one of these wake reasons, check the issue state and continue the work or mark it done.
## Project Setup Workflow (CEO/Manager Common Path)
@@ -189,6 +189,8 @@ The response also includes `blockedBy` and `blocks` arrays showing first-class d
}
```
Blocker wake semantics are strict: `issue_blockers_resolved` only fires when every blocker reaches `done`. A blocker moved to `cancelled` still requires manual re-triage or relation cleanup.
---
## Worked Example: IC Heartbeat