Skip to content

fix: render markdown artifacts authored under the default HTML type - #720

Merged
philmerrell merged 1 commit into
developfrom
feature/artifact-markdown-coercion
Jul 24, 2026
Merged

fix: render markdown artifacts authored under the default HTML type#720
philmerrell merged 1 commit into
developfrom
feature/artifact-markdown-coercion

Conversation

@philmerrell

Copy link
Copy Markdown
Contributor

Problem

Asking the agent to "create a banana bread recipe markdown artifact" produced an artifact that rendered as run-together raw Markdown source (#, ##, ** …) with a card badge reading HTML — not a formatted document. Repro conversation (prod): 33cb39eb-4d3c-4e72-94a9-904400243d4f.

Root cause

The Markdown pipeline works end-to-end (the writer wraps Markdown in an HTML render document via marked, the badge shows "MD", the render Lambda maps the type). The failure is at authoring time: create_artifact's content_type defaults to text/html. The model wrote raw Markdown as content but never set content_type="text/markdown", so service._put_object stored the Markdown verbatim as HTML — which the sandboxed iframe renders as plain text, and the DynamoDB row records text/html (hence the "HTML" badge).

Fix

Server-side safety net (the guarantee). HTML-typed content that lacks a standalone HTML document shell (<!doctype html> / <html>) is reclassified to text/markdown before storage, so it's wrapped into a proper render document and the DynamoDB row (badge + render-Lambda mapping) stays truthful. Real HTML documents, Markdown, and other MIME types pass through untouched. Applied in both create_artifact_record and update_artifact_record — the update path covers the inherited-type case, where there's no argument for the model to get right at all.

Docstring nudge (reduces how often the backstop fires). create_artifact's description now steers the model to author prose (reports, docs, recipes, mostly-text) as Markdown and to honor an explicit "markdown" request.

Why a content-based net rather than making content_type required

The bug was a type/content mismatch (HTML type declared over Markdown body), not an omitted value. A required argument forces the model to state a value, not a correct one, removes a genuinely good default for the majority-HTML case, and still can't help the update_artifact inherited-type path. The coercion validates the invariant we actually care about — declared type matches content shape — deterministically.

Cost note

The docstring adds ~60 tokens to the create_artifact description, which lives in the cacheable toolConfig prefix — only when the artifact tool is enabled, and it's a one-time-per-session prefix cost, not per-turn growth.

Tests

Added 4 regression tests; full artifact suite green (26 artifact-tool + 29 app-api artifact tests):

  • Markdown under the default type → reclassified + wrapped
  • Markdown under explicit text/html → reclassified
  • a genuine full HTML document → not touched, stored verbatim
  • an HTML artifact updated with Markdown (inherited type) → reclassified on the new version

Notes

  • Backend/authoring-path change only (no dev-server-observable surface) — verified via the test suite, not the browser preview.
  • Existing artifacts already stored as HTML won't retroactively change, but any new create/update — including an update_artifact on the banana-bread artifact — now renders correctly.

🤖 Generated with Claude Code

create_artifact's content_type defaults to text/html, so a request like
"create a markdown recipe artifact" produced raw Markdown stored verbatim
as HTML — the iframe then showed run-together `#`/`**` source and the card
badge read "HTML" instead of rendering the document.

Add a server-side safety net: HTML-typed content that lacks a standalone
HTML document shell (`<!doctype html>` / `<html>`) is reclassified as
text/markdown before storage, so the writer wraps it into a proper render
document and the DynamoDB row (badge + render-Lambda mapping) stays
truthful. Real HTML documents, Markdown, and other MIME types pass through
untouched. Applied in both create_artifact_record and update_artifact_record
(the update path covers the inherited-type case where there is no argument
for the model to get right).

Also steer the model up front: the create_artifact docstring now says to
author prose (reports, docs, recipes, mostly-text) as Markdown and to
honor an explicit "markdown" request, reducing how often the backstop fires.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@philmerrell
philmerrell merged commit 089917e into develop Jul 24, 2026
4 checks passed
@philmerrell
philmerrell deleted the feature/artifact-markdown-coercion branch July 24, 2026 13:47
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