Skip to content

fix(mcp): pinning a suggested contact is answered, not retried (product#4050) - #199

Merged
milstan merged 1 commit into
mainfrom
milstan/pin-unpin-contact-e2e
Sep 2, 2026
Merged

fix(mcp): pinning a suggested contact is answered, not retried (product#4050)#199
milstan merged 1 commit into
mainfrom
milstan/pin-unpin-contact-e2e

Conversation

@milstan

@milstan milstan commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

leadbay_pin_contact failed 43 of its 48 production calls in the 180 days to 2026-09-02; Sentry MCP-3A holds exactly 43 events, and 41 belong to one scheduled agent that pins a source:"paid" candidate id, which POST /contacts/{id}/pin can never resolve. The client's shared 404 hint says "Verify the ID is correct", which on this endpoint is false and reads to an agent as "retry", so pin and unpin now replace it with one naming the org-vs-paid split and saying not to retry, and both descriptions carry the same rule via a new shared snippet that also states pinning never steered enrichment.

Separately, pinned and pinned_by_ai have been on the backend's ContactPayload all along and every MCP shaping site dropped them, so a pin could be written but only inferred from recommended; they now pass through research_lead_by_id, get_contacts and get_lead_profile, marked 📌 in both markdown contact lists, with paid contacts deliberately carrying no pin key rather than a synthetic false.

Verified live against FR staging with the built branch (pinned false to true to false across pin and unpin); pnpm -r test and pnpm -r typecheck green.

Not fixed here, and the reason the agent reached for pin at all: on hosted there is still no way to enrich one named person, because leadbay_enrich_contacts sits in granularWriteTools behind LEADBAY_MCP_ADVANCED=1 and no granular tool has been called from a hosted IP in 30 days. Filed as leadbay/product#4050.

[Claude]: authored on Milan's behalf.

…ct#4050)

leadbay_pin_contact failed 43 of its 48 production calls in the 180 days to
2026-09-02. Sentry MCP-3A holds exactly 43 events, so every "contact not found"
in the mcp project is a pin call. 41 belong to one scheduled agent,
zoe+dogfood@leadbay.ai, across 12 days and MCP 0.26.0 through 0.33.2. It picks
the DG out of leadbay_research_lead_by_id and pins that id, which on an
unenriched company is a source:"paid" candidate. Pin is its third most used
tool and has never once succeeded.

POST /contacts/{id}/pin resolves through org_contacts only, so a paid candidate
id can never resolve there.

The client's shared 404 hint is "Verify the ID is correct" (client.ts:1048).
On this endpoint that is false: the id is correct, it is the wrong namespace.
An agent reads it as "look it up and retry" and does, in bursts of up to 10
calls in 13 seconds. pin and unpin now catch NOT_FOUND and replace the hint
with one naming the org-vs-paid split, saying not to retry, and naming the
tools that make the person pinnable.

Both descriptions carry the same rule via a new shared snippet, with the short
form in prefer_when so it lands in the first 600 chars every host reads. The
snippet also states that pinning does not steer enrichment: enrichment selects
by job title and pinnedBy plays no part. That was the second half of the
agent's mistake.

Separately, pinned was invisible. The backend's ContactPayload has carried
pinned + pinned_by_ai all along; every MCP shaping site dropped them, so a pin
could be written but only read back by watching recommended, which moves for
other reasons too. Now passed through in research_lead_by_id, get_contacts and
get_lead_profile, and marked in both markdown contact lists. Paid contacts get
no pinned key rather than a synthetic false, matching PaidContactPayload.

Verified live against FR staging with the built branch: pinned false to true to
false across pin and unpin, paid contacts carrying no pin state throughout.

Not fixed here: on hosted an agent still has no way to enrich one named person.
leadbay_enrich_contacts takes a contact_id and does exactly that, but sits in
granularWriteTools behind LEADBAY_MCP_ADVANCED=1, and no granular tool has been
called from a hosted IP in 30 days. That is why the agent reached for pin.
Filed as leadbay/product#4050.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 2, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-02T20:37:36.624581Z d1b856c PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d1b856c173

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// candidate rather than an org contact. Agents read that hint as "look
// it up again and retry" and hammer the endpoint (43 of 48 production
// pin calls in the 180 days to 2026-09-02 were this 404).
if (e?.code === "NOT_FOUND") throw { ...e, hint: NOT_PINNABLE_HINT };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Do not classify every contact 404 as a paid candidate

When contact_id is mistyped, stale, or belongs to a deleted org contact, this endpoint returns the same NOT_FOUND, but this branch replaces the accurate verification hint with one asserting that the ID is correct and directing the agent to enrich or add the person. Because the request carries no source information, the tool cannot distinguish those cases from a paid candidate; preserve both possible causes (while still discouraging blind retries) so agents do not conceal bad IDs or create duplicate contacts.

Useful? React with 👍 / 👎.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well-scoped fix: replaces the misleading 404 hint on pin/unpin with one that correctly explains the org-vs-paid split and tells the agent not to retry, and threads pinned/pinned_by_ai through the three contact-shaping sites. Verified the generated tool-description text matches the .md.tmpl + shared snippet source, the paid/org asymmetry (no synthetic pinned:false on paid contacts) is handled consistently everywhere, the 404-rewrite is safe (errors are plain objects, not Error instances, so the spread preserves all fields), and new tests are all in new files per convention. One minor coverage gap noted inline; no blocking issues found.

enrichment: c.enrichment,
// Org contacts only — the backend's PaidContactPayload carries no pin
// state, because a paid candidate cannot be pinned.
pinned: c.pinned ?? false,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shaping site gets the same pinned/pinned_by_ai passthrough as research-lead-by-id.ts and get-contacts.ts, but unlike those two, it has no test coverage here — contact-pin-state-passthrough.test.ts only exercises research_lead_by_id and get_contacts. Since existing test files can't be modified, this needs a new test (or an addition to the new pin-state test file) covering get_lead_profile.

@milstan
milstan merged commit 3877435 into main Sep 2, 2026
2 checks passed
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