paperclipai/paperclip · error

Could not replace conversation boundary

Error message

Could not replace ${provider} conversation boundary

What it means

`patchConversationBoundary` rewrites the conversation-boundary explanatory copy inside each provider SVG by finding fixed coordinate anchors (`<text x="504" y="234"` desktop, `<text x="16" y="260"` mobile) and an end marker (`<line ...>`), then splicing in wrapped copy. Missing anchors mean the base SVG layout drifted from what the patcher assumes, so it throws per provider.

Solutions

  1. Regenerate or inspect a base SVG and update `startToken`/`endToken` in `patchConversationBoundary` to the current coordinates and attribute order.
  2. Keep the boundary placeholder emission and the patcher anchors in one shared constant set so they cannot drift.
  3. If attribute order changed, match on the minimal stable substring or use a regex instead of exact indexOf tokens.
  4. Re-run the generator and verify every provider/mobile combination patches cleanly.

Example fix

// before
const startToken = mobile ? '<text x="16" y="260"' : '<text x="504" y="234"';
// after (anchors moved)
const startToken = mobile ? '<text x="16" y="280"' : '<text x="504" y="254"';
Defensive patterns

Strategy: validation

Validate before calling

const hasBoundaryAnchors = (svg, mobile) => {
  const s = mobile ? '<text x="16" y="260"' : '<text x="504" y="234"';
  const e = mobile ? '<line x1="16" y1="348"' : '<line x1="504" y1="278"';
  return svg.includes(s) && svg.includes(e);
};

Type guard

const canPatchBoundary = (svg, mobile) => svg.indexOf(mobile ? '<text x="16" y="260"' : '<text x="504" y="234"') >= 0;

Try / catch

try {
  svg = patchConversationBoundary(svg, provider, mobile);
} catch (err) {
  if (String(err.message).includes("conversation boundary")) {
    console.error(`Boundary anchors missing for ${provider}${mobile ? " (mobile)" : ""}; resync patchConversationBoundary with the SVG emitter.`);
  } else throw err;
}

Prevention

When it happens

Trigger: Running the v7 generator when the base SVG for a provider no longer contains the boundary text/line at the expected coordinates — layout changes moved the text, the block was removed, attribute order changed (e.g. `<line y1="278" x1="504" ...>`), or mobile/desktop variants were restructured.

Common situations: Adjusting SVG spacing/typography in shared helpers so y=234/260 or the line at y1=278/348 moved; deleting the placeholder boundary block; reformatting attribute order in the SVG emitter.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18). Data as JSON: /api/errors/751db808ba6736ca. Report an issue: GitHub.

Appendix: source

Thrown at doc/plans/chat-adapters/generate-wireframes-v7.mjs:201

  const end = svg.indexOf(marker, start);
  if (start < 0 || end < 0) throw new Error(`Could not replace ${provider} ${tab} navigation`);
  const height = Number(svg.match(/<svg[^>]+height="(\d+)"/)?.[1]);
  return `${svg.slice(0, start)}${endpointNav(provider, tab, height)}${svg.slice(end + 4)}`;
}

const conversationBoundaryCopy = {
  Slack: "A channel root and its replies are one task. The first mention in an existing thread binds it. A DM has one open task; after completion the next message starts another.",
  GitHub: "An issue, pull-request conversation, or inline review thread binds once to one Paperclip task.",
  "Microsoft Teams": "A channel post and its replies are one task. A personal or group chat has one open task; after completion the next message starts another.",
  Telegram: "A DM or ordinary group has one open task; after completion the next addressed message starts another. A forum topic has one stable topic-to-task binding."
};

function patchConversationBoundary(svg, provider, mobile) {
  const startToken = mobile ? '<text x="16" y="260"' : '<text x="504" y="234"';
  const endToken = mobile ? '<line x1="16" y1="348"' : '<line x1="504" y1="278"';
  const start = svg.indexOf(startToken);
  const end = svg.indexOf(endToken, start);
  if (start < 0 || end < 0) throw new Error(`Could not replace ${provider} conversation boundary`);
  const copy = conversationBoundaryCopy[provider];
  const replacement = mobile
    ? multiline(16, 260, wrap(copy, 46, 4), 12, "#666", 18)
    : multiline(504, 234, wrap(copy, 88, 2), 14, "#666", 20);
  return `${svg.slice(0, start)}${replacement}${svg.slice(end)}`.replaceAll("active task", "open task");
}

const sharedDefinitions = [
  { id:"01", slug:"connectors-catalog", title:"Connectors", subtitle:"Connect tools and places where people talk to agents.", group:"Start", tab:"Shared", annotations:["The existing Apps catalog remains the entry point.", "Filters separate chat and tool methods.", "Each connector row has one Connect action.", "Connection state remains visible in the catalog."], rationale:"The current Connectors surface remains canonical." },
  { id:"02", slug:"connection-purpose", title:"Choose how to connect", subtitle:"Shown for every connector that supports both chat and tool methods.", group:"Start", tab:"Shared", annotations:["The existing connection wizard shell and selected provider are reused.", "Chat with an agent is the incoming-conversation path.", "Use this connection as an agent tool is the outbound tool/credential path.", "Single-purpose providers skip the choice."], rationale:"The registry drives the same direction choice for every dual-surface connector." },
  { id:"03", slug:"choose-agent", title:"Which agent do you want to chat with?", subtitle:"Choose the one agent represented by this connection.", group:"Start", tab:"Shared", annotations:["The existing agent selector is reused.", "Only active agents can be selected.", "One selection is required.", "Continue begins provider setup."], rationale:"This is the only shared Paperclip-specific setup decision." },
  { id:"11", slug:"bound-task", title:"Externally bound task", subtitle:"A normal Paperclip task with explicit publication and detach controls.", group:"Paperclip", tab:"Task", annotations:["The task shows its external source.", "External actors remain attributed.", "Publishing back to the provider is explicit for human comments.", "The agent remains locked until detach."], rationale:"External work stays in the ordinary governed task experience." },
  { id:"12", slug:"agent-channels", title:"Agent Channels", subtitle:"See every provider identity representing this agent.", group:"Paperclip", tab:"Agent", annotations:["Channel identities are summarized per provider.", "Health and recent tasks remain visible.", "Connections open in Connectors.", "Connect a channel preselects this agent."], rationale:"Agent detail summarizes endpoints while Connectors manages them." }
];

const sharedScreens = sharedDefinitions.map((screen) => {
  for (const suffix of ["", "-mobile"]) writeFileSync(join(out, `${screen.id}-${screen.slug}${suffix}.svg`), readFileSync(join(previous, `${screen.id}-${screen.slug}${suffix}.svg`), "utf8"));
  return { ...screen, desktopSize:"1280×800", mobileSize:"375×812" };

View on GitHub (pinned to 3f1d897a7c)