Execution Semantics for Issue Dependencies and Blockers in Paperclip AI

Paperclip AI implements issue dependencies as a directed-acyclic graph where blockedBy edges enforce hard execution constraints, with workflowSort providing topological ordering and the attention service generating actionable items for stalled blockers.

This article examines how Paperclip AI encodes, sorts, and acts on issue blocker relationships across its TypeScript codebase. The execution semantics span three architectural layers: shared type definitions, UI presentation logic, and server-side attention generation.

Data Model: Encoding Blocker State and Diagnostics

The foundation of Paperclip's execution semantics resides in [packages/shared/src/types/issue.ts](https://github.com/paperclipai/paperclip/blob/master/packages/shared/src/types/issue.ts). This file defines the canonical shape of blocker attention diagnostics that flow through the entire system.

IssueBlockerAttentionState Enum

The state field uses a four-value enum to communicate blocker urgency:

  • "none" – no active dependency concern
  • "covered" – blocker exists but is being addressed
  • "stalled" – blocker is stuck and requires intervention
  • "needs_attention" – blocker demands immediate human action

Key Diagnostic Fields

The IssueBlockerAttention interface (lines 399–414) carries:

Field Purpose
reason Why the blocker exists (e.g., "active_dependency")
terminalBlockerIssueId The leaf blocker requiring human action
blockingTreeLive Boolean indicating whether any descendant is already running

These fields enable both the UI and server to make precise decisions about when and how to surface blocker-related actions.

Topological Sorting: The workflowSort Algorithm

The UI must present issues in an order that preserves dependency chains. The [ui/src/lib/workflowSort.ts](https://github.com/paperclipai/paperclip/blob/master/ui/src/lib/workflow-sort.ts) utility implements a chain-aware topological sort that keeps linear blocker sequences contiguous.

Core Algorithm Structure

The workflowSort function builds two auxiliary structures:

// From workflowSort.ts lines 90–104
const successors = new Map<string, string[]>();  // issueId → array of blockers
const inDegree = new Map<string, number>();      // count of incoming blocker edges

It then performs a chain walk: starting from each root, it follows the successors map as long as exactly one successor exists. This produces contiguous chains for the common case of A ← B ← C dependencies.

Fallback Behaviors

The algorithm includes defensive measures for edge cases:

  1. Cross-parent blockers – When a chain branches (more than one successor or predecessor), the walk breaks and ordering falls back to timestamp/id tie-break (see the if (succIds.length !== 1) break; guard).

  2. Missing blockers – Issues referencing non-existent blockers still display the blocker chip but receive no special ordering treatment (comment lines 14–15).

  3. Cycle detection – If the output size doesn't match input size after sorting, the function degrades to a pure timestamp/id sort (lines 106–108), preventing infinite loops when the API's acyclicity validation fails.

The overall complexity is O(N + E) time, suitable for large boards.

Attention Generation: From Blocker State to Action

The server-side [attention.ts](https://github.com/paperclipai/paperclip/blob/master/server/src/services/attention.ts) service bridges blocker diagnostics and user-facing actions. Located in [server/src/services/attention.ts](https://github.com/paperclipai/paperclip/blob/master/server/src/services/attention.ts#L1444-L1526), it queries blocked issues and produces attention items—structured recommendations for the UI.

Query and Candidate Selection

Lines 44–82 fetch all issues with status: "blocked". For each, the service examines blockerAttention.state to determine severity.

Terminal Blocker Handling

Lines 108–154 implement the critical logic for stalled or needs-attention blockers:

// Simplified from attention.ts lines 108–154 and 322–355
if (blockerAttention?.state === "stalled" && !blockerAttention.blockingTreeLive) {
  add(createItem({
    sourceKind: "blocker_attention",
    subject: issueSubject(prefix, terminalSummary),
    whyNow: `Blocks ${blockedTaskCount} tasks and needs human attention.`,
    decisionVerbs: decisionVerbs(
      { id: "unblock", label: "Unblock", description: "Repair the blocker" },
      { id: "reassign", label: "Reassign", description: "Assign to a live owner" },
      { id: "nudge", label: "Nudge", description: "Prompt the current owner" }
    ),
    severity: "high"
  }));
}

The terminalBlockerIssueId field ensures users act on the actual leaf blocker rather than intermediate nodes in a dependency chain.

Execution Semantics Summary Table

Situation System Behavior Implementation Location
Issue A blocked by B A cannot exit "blocked" status until B resolves blockedBy field + issuesSvc.update
Linear chain A←B←C UI renders A → B → C contiguously workflowSort.ts lines 90–104
Cross-parent blocker Chain walk breaks; timestamp fallback workflowSort.ts successor guard
Stalled blocker High-severity attention item with Unblock/Reassign/Nudge verbs attention.ts lines 108–154
Missing blocker reference Blocker chip displays; no ordering special case workflowSort.ts comment lines 14–15
Cycle in dependencies Degrade to timestamp/id sort workflowSort.ts lines 6–8, 106–108

Practical Code Examples

Creating a Blocker Relationship

// Client → server: establish dependency
await issuesService.update(issueId, {
  status: "blocked",
  blockedByIssueIds: [blockerId]  // Must belong to same company
});

Sorting Issues with Blocker Awareness

import { workflowSort } from "@/lib/workflowSort";

const sorted = workflowSort(issues);  // Issue[] from API
// Preserves linear chains, falls back to creation time otherwise

Generating Attention Items Server-Side

The attention service pattern shows how to surface actionable intelligence from blocker state—checking blockerAttention.state, validating !blockingTreeLive, and constructing decision verbs for the UI.

Key Implementation Files

File Path Responsibility
Shared Types [packages/shared/src/types/issue.ts](https://github.com/paperclipai/paperclip/blob/master/packages/shared/src/types/issue.ts) Issue, IssueBlockerAttention, state enums
Workflow Sort [ui/src/lib/workflowSort.ts](https://github.com/paperclipai/paperclip/blob/master/ui/src/lib/workflow-sort.ts) Topological + chain-aware ordering
Attention Service [server/src/services/attention.ts](https://github.com/paperclipai/paperclip/blob/master/server/src/services/attention.ts) Item generation for stalled blockers
Issue Service [server/src/services/issues.ts](https://github.com/paperclipai/paperclip/blob/master/server/src/services/issues.ts) API for blockedByIssueIds updates
UI Blocker Chip [ui/src/lib/blockedInbox.ts](https://github.com/paperclipai/paperclip/blob/master/ui/src/lib/blockedInbox.ts) Blocker metadata rendering
Pipeline Types [ui/src/api/pipelines.ts](https://github.com/paperclipai/paperclip/blob/master/ui/src/api/pipelines.ts#L177) Blocker exposure to pipeline views

Summary

  • Safety: The blockedBy field enforces hard dependencies—no issue advances while blockers remain unresolved.
  • Predictability: workflowSort maintains contiguous blocker chains so users perceive dependency structure clearly.
  • Actionability: The attention service transforms stalled/needs_attention states into specific UI verbs (Unblock, Reassign, Nudge).
  • Resilience: Edge cases (missing blockers, cycles, cross-parent dependencies) degrade gracefully without system failure.

These semantics operate across the full stack—from type definitions in packages/shared through UI ordering in ui/src/lib to server intelligence in server/src/services.

Frequently Asked Questions

How does Paperclip prevent circular dependencies between issues?

The API layer validates acyclicity before persisting blockedByIssueIds. As a defensive measure, workflowSort detects size mismatches post-sort (lines 106–108) and falls back to timestamp-based ordering if cycles somehow reach the client.

What happens when a blocker issue is deleted?

The blockedBy reference persists but becomes a phantom blocker. The UI still renders the blocker chip (per workflowSort.ts comment lines 14–15), but the sorting algorithm treats it as absent—no special chain positioning applies.

Can multiple issues block a single dependent issue?

Yes. However, workflowSort only produces contiguous chains for linear dependencies (single successor, single predecessor). When an issue has multiple blockers, the chain walk breaks and ordering falls back to timestamp/id tie-break.

What triggers a "needs_attention" blocker state?

The attention service (lines 44–82) identifies blockers where human intervention is required—typically when blockingTreeLive is false and diagnostics indicate the blocker is not being actively addressed. This generates high-severity attention items with specific resolution verbs.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →