Skip to content

fix(copy): trigger copy claimed a push the transport does not do; retire "wake an agent"#784

Merged
choraria merged 4 commits into
mainfrom
fix/trigger-copy-truth-debt
Jul 24, 2026
Merged

fix(copy): trigger copy claimed a push the transport does not do; retire "wake an agent"#784
choraria merged 4 commits into
mainfrom
fix/trigger-copy-truth-debt

Conversation

@choraria

Copy link
Copy Markdown
Contributor

What

triggers.wait is a caller-driven short poll over the durable event log (ADR-0106). Server-push was considered and rejected there — MCP's server-initiated messages are at-most-once and drop events across a disconnect — and survives only as a possible future latency hint, "never the guarantee itself."

First-contact copy said the opposite. Found while a user registered a trigger, POSTed to their ingest URL, and watched nothing happen — then went debugging a working system against a mental model we handed them.

The two defects

1. "Push, not poll" (false). The dashboard triggers page read "a pushed trigger, not a poll." /mcp/wake-an-agent's frontmatter said "an event-driven primitive, not polling" while line 63 of the same page correctly called it a short poll. quickstart.mdx told readers to have an agent "block on triggers.wait until a matching webhook lands."

The deep layers — ADR-0106, /mcp/trigger-semantics, the db and contract comments (packages/db/src/agent-triggers.ts:4 literally says "It does not push or 'wake' the agent") — were always honest. The drift concentrated in ledes, frontmatter descriptions, and nav blurbs: exactly where the mental model forms.

2. Fabricated eventTypes (worse). The /product/agent-triggers visual showed triggers.create { endpointId, eventTypes: ["issue.updated"] }. There is no eventTypes on a trigger — it binds to one endpoint and yields everything that endpoint captures. The field belongs to subscriptions.create, and the input schema strips unknown keys rather than rejecting them. So a developer following the card would send a filter, have it silently dropped, and receive the endpoint's full stream believing it was filtered. Nothing errors, anywhere.

Changes

Copy — dashboard, docs, README, MCP tool descriptions, CLI help now say who drives the cadence. triggers.create's tool description states outright that nothing will call you and that a client which only registers receives nothing; that text is read by the model writing the client. /mcp/wake-an-agent gets a callout aimed at the reader whose POST did nothing, and its flow diagram's last hop now originates at the agent, because it does. /mcp/trigger-semantics gets a new "one endpoint, everything it captures" section documenting the absent filter and the silent strip.

Guardsscripts/trigger-copy-guard.mjs bans phrases false in any context (negation-aware, so the honest "does NOT block until" still passes) and adds a positive obligation: pages introducing the primitive must name the mechanism, so a page can't drop the lie and leave a vague claim. Surfaces are discovered by content, not listed — the first cut of this guard carried 13 hand-picked files and went green while quickstart.mdx lied. agent-trigger-card.test.tsx now validates shown arguments against the capability input schema; the old test pinned tool names, which is why an invented argument sailed through for a year.

Verification

Each guard confirmed RED on the real defect before the fix — including re-introducing eventTypes to check the new assertion fails with the right message (triggers.create has no "eventTypes" input — it would be silently ignored).

Green: full lint chain, 652 script tests, www 553, web 1552, mcp 120, cli 839, 33/33 typecheck.

⚠️ Needs human eyeball before merge

Per the AGENTS.md human-UI-testing hard stop, this is all user-facing copy and I can't verify rendering. Please check:

  • Dashboard /org/<slug>/triggers — the lede is longer than before and now contains an inline <code>; check it doesn't wrap badly, and the same for the revoke dialog.
  • Docs<Warning> on /mcp/wake-an-agent and <Note> on /mcp/trigger-semantics render correctly, and the rewritten mermaid diagram (now with a bidirectional agent↔log pair) lays out sensibly.
  • Judgement call to confirm: I kept the "wake an agent" page names, URLs, and nav titles. An agent is woken — by its own loop — and the URLs are indexed. I made the mechanism honest at first contact rather than renaming the feature. Say the word if you'd rather retire the framing entirely.

🤖 Generated with Claude Code

https://claude.ai/code/session_012hee4wHe7WLq6xTafqNDRY

`triggers.wait` is a caller-driven short poll over the durable event log
(ADR-0106). Server-push was considered and REJECTED there — MCP's
server-initiated messages are at-most-once and drop events across a
disconnect — and is listed as a possible future latency hint, "never the
guarantee itself."

First-contact copy said otherwise. The dashboard's triggers page read "a
pushed trigger, not a poll"; /mcp/wake-an-agent's own frontmatter said "an
event-driven primitive, not polling" while line 63 of the same page correctly
called it a short poll; quickstart told readers to have an agent "block on
triggers.wait until a matching webhook lands." The deep pages (ADR-0106,
/mcp/trigger-semantics, the db + contract comments) were always honest — the
drift concentrated exactly where the mental model forms. The observable cost:
register a trigger, POST to the ingest URL, watch nothing happen, and start
debugging a working system.

Separately, the /product/agent-triggers visual showed
`triggers.create { endpointId, eventTypes: [...] }`. There is no eventTypes on
a trigger — it binds to ONE ENDPOINT and yields everything that endpoint
captures. The field belongs to subscriptions.create, and the input schema
strips unknown keys rather than rejecting them, so following the card produced
a silently UNFILTERED trigger with no error anywhere.

Copy:
- dashboard, docs, README, MCP tool descriptions, CLI help: say who drives the
  cadence. `triggers.create`'s description now states outright that nothing
  will call you and a client that only registers receives nothing — that text
  is read by the model writing the client.
- /mcp/wake-an-agent: a callout for the reader whose POST did nothing, and the
  flow diagram's last hop now originates at the agent, since it does.
- /mcp/trigger-semantics: new "one endpoint, everything it captures" section
  documenting the absent filter and the silent strip.
- www card + product page: real arguments, per-endpoint scope.

Guards:
- scripts/trigger-copy-guard.mjs — phrases false in any context (negation
  included), plus a positive obligation: pages introducing the primitive must
  NAME the mechanism, so a page can't drop the lie and leave a vague claim.
  Surfaces are DISCOVERED by content, not listed: the first cut of this guard
  carried 13 hand-picked files and went green while quickstart.mdx lied.
- agent-trigger-card.test.tsx now validates shown ARGUMENTS against the
  capability input schema. The old test pinned tool NAMES, which is why an
  invented argument sailed through it.

Verified: guard RED on each defect before the fix (incl. re-introducing
eventTypes to confirm the new assertion fails with the right message); lint
chain + 652 script tests, www 553, web 1552, mcp 120, cli 839 all green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012hee4wHe7WLq6xTafqNDRY
@mintlify

mintlify Bot commented Jul 24, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
webhook-co 🟢 Ready View Preview Jul 24, 2026, 7:45 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

The verb was the dishonest part, not the noun. "Trigger" is what the API
calls it (`triggers.*`), what the dashboard calls it, and what the www
product page calls it; "wake" described a push that ADR-0106 explicitly
rejected. Renaming the verb and keeping the noun leaves docs connected to the
surface they document.

  /mcp/wake-an-agent      → /mcp/agent-triggers
  /recipes/wake-an-agent  → /recipes/agent-triggers

Both old URLs are indexed, so docs.json gains a `redirects` block (permanent)
rather than leaving every existing inbound link on a 404.

The MCP page is rewritten from the ground truth rather than patched: it now
opens with what a trigger IS — a durable subscription to one endpoint plus a
cursor — and derives the three things worth caring about from that (can't
miss an event, can't process one twice, can't be fooled). The two steps are
numbered. The dashboard lede is shortened to the same shape and no longer
needs the word "woken" at all.

Verified: lint chain, docs-nav-guard (114 pages, links resolve),
trigger-copy-guard (34 surfaces), web 1552, www 553.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012hee4wHe7WLq6xTafqNDRY
@choraria choraria changed the title fix(copy): trigger copy claimed a push the transport does not do fix(copy): trigger copy claimed a push the transport does not do; retire "wake an agent" Jul 24, 2026
@choraria

Copy link
Copy Markdown
Contributor Author

Follow-up: framing retired (founder call)

/mcp/wake-an-agent/mcp/agent-triggers, /recipes/wake-an-agent/recipes/agent-triggers.

The verb was the dishonest part, not the noun — "trigger" is what triggers.*, the dashboard, and /product/agent-triggers already call it, so keeping it leaves the docs connected to the surface they document. Both old URLs are indexed, so docs.json gains a permanent redirects block rather than 404ing every inbound link.

The MCP page is rewritten from the ground truth rather than patched — it opens with what a trigger is (a durable subscription to one endpoint, plus a cursor) and derives the three properties that follow: can't miss an event, can't process one twice, can't be fooled. Steps are numbered. The dashboard lede shrank to the same shape and no longer needs the word "woken" at all.

Re-verified: lint chain, docs-nav-guard (114 pages, links resolve), trigger-copy-guard (34 surfaces), web 1552, www 553, 33/33 typecheck.

The shorter /mcp/agent-triggers href narrowed the cell and left the column
padding stale. Caught by CI's format-check rather than the pre-commit hook,
whose lint-staged glob covers *.md but not *.mdx.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012hee4wHe7WLq6xTafqNDRY
@choraria
choraria merged commit 398acaa into main Jul 24, 2026
33 checks passed
@choraria
choraria deleted the fix/trigger-copy-truth-debt branch July 24, 2026 08:17
@mintlify

mintlify Bot commented Jul 24, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
webhook-co 🟡 Building Jul 24, 2026, 7:45 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant