Skip to content

Render the template's prompt when launching an agent via MCP - #941

Merged
selfcontained merged 5 commits into
mainfrom
agt_75b8f7551dcf/build-fix-mcp-launch-template-prompt-bug
Aug 12, 2026
Merged

Render the template's prompt when launching an agent via MCP#941
selfcontained merged 5 commits into
mainfrom
agt_75b8f7551dcf/build-fix-mcp-launch-template-prompt-bug

Conversation

@selfcontained

@selfcontainedselfcontained commented Aug 11, 2026

Copy link
Copy Markdown
Owner

The bug

dispatch_launch_agent accepted a templateId but only ever used it for worktree settings and record-keeping — template.prompt was never read. An MCP caller passing templateId plus a short prompt got an agent whose entire prompt was that short string: every one of the template's own instructions was silently dropped. The web UI's launch path (TemplateService.launchTemplate) has always rendered it, so the two launch surfaces disagreed.

Impact was broad — every MCP caller using templateId, not just one workflow — and invisible, since the launch succeeds and the agent just behaves as if the template said nothing.

The fix

One assembly point.renderTemplatePrompt(template, args, appendix?) in the new apps/server/src/templates/launch-prompt.ts is now the only place a template's prompt becomes an agent's startup prompt — substitute args, then the caller's own text, then the self-improvement footer. TemplateService.launchTemplate sits on it too. Both paths previously encoded that rule independently, which is the same class of divergence this PR exists to fix.

Free text → args. The UI collects one value per {{D:...}} arg from a form; an MCP caller has only free text, so renderTemplatePromptFromFreeText resolves it:

  • dispatch_launch_agent takes an optional templateArgs map keyed by arg name.
  • A single arg left unset after templateArgs takes the caller's prompt. Anything not consumed follows the rendered template.
  • Args nobody supplies render empty rather than failing the launch or leaking literal {{D:...}}; the launch result names them.

Discovery.get_template and list_templates now report a parsed promptArgs list — the same parse the UI's launch form uses to build its fields. Without it a caller would have to recognise placeholder syntax inside the prompt text to know what templateArgs wants.

model, fullAccess, agentType, and cwd still inherit from the launching agent as the tool's docs promise — full UI parity there would change behavior for every existing MCP caller and is out of scope for this bug.

Two ways caller args were silently mishandled

Both found by review, both fixed with tests and confirmed live:

  • A templateArgs key matching no arg was dropped — and the arg it was meant for then counted as unset, so the free-text prompt took that slot and nothing warned. {prUrl: "..."} against {{D:PR URL|required}} produced Review review it please. with an empty note. Unmatched keys are now reported, and their presence suppresses the one-arg fill shortcut, since an unmatched key is evidence the caller was aiming a different value at that slot.
  • An arg written in two cases half-rendered. Values resolve per-arg but substituteArgs resolves per-occurrence, so a name-keyed value only reached the occurrence whose spelling matched: {{D:Repo}} / {{D:repo}} with {Repo: "dispatch"} rendered Repo: dispatch / again: . Args now collapse into key space before rendering.

Verification

Unit tests cover both renderers and the handler path — lone arg, no args, templateArgs, unset args, unrecognized keys, case-collision, repeated args, Object-member arg names, caller values containing {{D:...}} syntax, appendix-before-footer ordering, and the promptArgs discovery half through both get_template and list_templates.

Exercised live over the real MCP endpoint against an isolated dev stack, reading each launched claude process's argv to confirm what it actually received:

LaunchPrompt the agent got
2 args + templateArgsReview the diff for security. then the caller's prompt
1 arg, no args passedcaller's prompt substituted into it, plus the self-improvement footer
2 args, none passedReview for . then the caller's prompt; result named both unset args
misspelled keyReview . then the caller's prompt; result named the unrecognized key and the unfilled arg
arg in two casesRepo: dispatch / again: dispatch

pnpm run check, pnpm run test, and pnpm run test:e2e all pass.

Reviews

Architecture and backend-security personas both reviewed; nine findings between them, all fixed and verified by the reviewers.

Follow-up (not in this diff)

The "Idea Inbox" template works around this bug by fetching the Build Idea template and substituting the arg itself, then passing both the substituted text andtemplateId. Once this ships, that stopgap would nest the rendered prompt inside the arg. Its instructions need updating after the fix is live on the server — merged is not deployed. Feature-detect: the new server's dispatch_launch_agent has templateArgs and get_template returns promptArgs.

selfcontainedand others added 5 commits August 11, 2026 15:58
dispatch_launch_agent accepted a templateId but only ever used it for
worktree settings and record-keeping — template.prompt was never read, so
an MCP-launched agent's entire prompt was the caller's short string and
every one of the template's own instructions was silently dropped. The
web UI's launch path (TemplateService.launchTemplate) has always
rendered it.
Launching a template is a request for the template's instructions, so the
template prompt is now always used. Its placeholders fill from a new
templateArgs param; a single remaining placeholder absorbs the caller's
free-text prompt, which is the unambiguous case. Anything the caller said
that did not become a placeholder value is appended under a heading
rather than dropped, and placeholders nobody supplied render empty with a
note back to the caller instead of failing the launch. Templates with
selfImprove set get the same footer the UI path adds.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A placeholder named after an Object member ({{D:toString}},
{{D:constructor}}) read as already supplied off the prototype chain, so it
rendered a built-in into the prompt instead of the caller's value.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A template whose prompt has {{D:...}} variables cannot render from a single
free-text prompt alone, so dispatch_launch_agent takes an optional
templateArgs map keyed by variable name. A single variable left unset after
templateArgs takes the caller's free text — the unambiguous case, and the
common one; anything not consumed follows the rendered template.
Callers had no way to learn a template's variable names short of
recognising the placeholder syntax inside its prompt text, so get_template
and list_templates now report a parsed `args` list alongside the record.
Variables nobody supplies still render empty rather than failing the launch,
and the launch result names them.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both launch surfaces independently encoded "substitute args, then append the
self-improvement footer" — the same class of divergence this branch exists to
fix. renderTemplatePrompt is now the one place a template's prompt becomes an
agent's startup prompt, and TemplateService.launchTemplate sits on it too.
The MCP-only job of resolving free text into args keeps its own entry point,
renderTemplatePromptFromFreeText.
Two ways a caller's args were silently mishandled:
A templateArgs key matching no arg was dropped, and the arg it was meant for
then counted as unset — so the free-text prompt took that slot and nothing
warned. Unmatched keys are now reported, and their presence stops free text
from filling a slot the caller was clearly aiming at.
An arg written in two cases ({{D:Repo}} and {{D:repo}}) half-rendered: values
resolve per-arg but substituteArgs resolves per-occurrence, so a name-keyed
value only reached the occurrence whose spelling matched. Args now collapse
into key space before rendering.
Also: real types on the template crud callbacks instead of unknown, promptArgs
(not args) as the reported field so it can't be shadowed by a future column,
one term for the concept in every description, and coverage for the discovery
half.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…umer
The prompt/templateArgs descriptions stated the fill rule unconditionally,
so a caller who typos a key on a single-arg template would predict the
opposite of what happens. Both now say an unrecognized key suppresses the
shortcut.
RenderedTemplatePrompt.unknown becomes unknownArgs, matching what the
handler and the caller-facing note already call it — and reading less like
the TS keyword.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@selfcontained
selfcontained merged commit f3c6122 into mainAug 12, 2026
1 check passed
@selfcontained
selfcontained deleted the agt_75b8f7551dcf/build-fix-mcp-launch-template-prompt-bug branch August 12, 2026 04:02
selfcontained added a commit that referenced this pull request Aug 13, 2026
The #945/#946 wave added five tools and changed how three others are
addressed. #945 removed get_agent_history and get_parent_context from
every doc surface in the same commit that introduced
dispatch_review_get_feedback, brain_get_event, brain_get_list_item and
whiteboard_howto — so the lists looked freshly maintained while being
short by five entries.
- docs-pane Repo Tools "Built-in tools": the five new tools, plus pin
update-by-id, dispatch_pins batch writes with merge/replace, delete by
id/ids/group, and reading one pin back whole by id. A closing paragraph
states the list/detail policy response.ts now encodes, so an agent
reading the docs knows a truncated listing has a matching full read.
- dispatch_launch_agent: with a templateId the template's own prompt is
now rendered and filled from templateArgs (#941) — it was previously
used only for worktree settings, and the bullet still described it as
just "or template".
- media.tsx Pins tab: pin groups collapse under a heading with a member
count, groups over eight start collapsed, and the choice persists per
agent and group (#946).
- automations.tsx job-agent lists: the new tools, plus persona_templates
/ persona_upsert / persona_validate and dispatch_archive_agent, which
are in JOB_TOOLS but were never enumerated there.
- README interactive-agents table and the persona-agents list.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for freeto 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

@selfcontained