Last batch I dispatched sixty writer agents in parallel. Eight minutes later sixty MDX files landed in the repo. Fourteen of them had broken FAQ headings and ten had empty takeaway boxes, because the writers all pattern-matched against the same wrong component usage in an earlier article. The audit script I ran caught link density and word count and anonymization. It did not grep the component props. This post is the five greps I now run before any batch of parallel writers starts.
- 01
grep component prop names
FaqItem q=, KeyTakeaways children
skip14 + 10 broken files - 02
grep MDX parse hazards
<N percent, nested double quotes
skipopaque build error - 03
diff registry vs references
mdx-components.tsx vs output
skipsilent blank elements - 04
dry-run one MDX through next build
one writer, full stages, one compile
skip60 files fail identically - 05
wrap 1-4 in a pre-dispatch script
exit non-zero, abort dispatch
skipchecks run inconsistently
Run these five greps before you dispatch
The whole thing runs in about 15 seconds against a 60-article batch. In order:
- Grep every MDX component's real prop names from source and fail the dispatch if writer output uses the wrong ones.
- Grep for MDX parse hazards: number-space patterns and double-quoted attributes with inner double quotes.
- Diff the component registry against the template examples your writer fleet will copy from.
- Dry-run one representative MDX through the build before green-lighting the other fifty-nine.
- Wrap steps 1-4 in a pre-dispatch script so you cannot forget.
Anything that fails, abort the dispatch. You save the tokens and the cleanup time. Everything below is the reasoning per item and the actual grep lines I use.
Check 1: grep each MDX component's real prop names
The first time this bit me was an <FaqItem> prop drift. The component signature is:
export function FaqItem({ q, children }: { q: string; children: ReactNode })
It accepts q, not question. A writer in an early batch had used question="...", the MDX still compiled, the JSX attribute just got dropped as an unknown prop, and the FAQ rendered with blank headings. The next writer pattern-matched against that article. By the time I noticed, fourteen articles in the batch had silent blank-header FAQs.
Same shape on <KeyTakeaways>. The real signature:
export function KeyTakeaways({ title, children }: { title?: string; children: ReactNode })
The component expects <li> elements as children. A writer had used items={[...]}, which MDX accepted as a valid prop on the React component, and then rendered nothing because the component only iterates children. Ten articles shipped with empty takeaway boxes before a human review caught it.
The grep I run now against the batch output folder before dispatch completes:
# Any wrong prop on FaqItem or KeyTakeaways fails the build gate
grep -l 'FaqItem question=' src/content/writing/*.mdx && echo "BROKEN: FaqItem uses q= not question=" && exit 1
grep -l 'KeyTakeaways items=' src/content/writing/*.mdx && echo "BROKEN: KeyTakeaways uses children, not items=" && exit 1
The more durable fix is a reference doc that lists every component's exact prop signature with a canonical example, and a writer common-instructions file that cites it. When you add a new component to the MDX library, you update the reference, the writer instructions, and the audit script in the same commit.
If you skip this check: every new batch inherits whatever prop drift is in the last batch. Writers pattern-match the bug forward. Blast radius grows each round.
Check 2: grep for MDX parse hazards
MDX is stricter than markdown because it tries to parse JSX inside your prose. A few character patterns that are correct English read like broken JSX:
Number-space after an angle bracket. A writer writes a phrase like conversion stays under <2 percent in prose. MDX sees <2 and tries to open a JSX tag named 2. Build fails with an opaque error pointing at a line three pages away.
Double-quoted attribute values containing inner double quotes. A writer writes <FaqItem q="Do "As seen in" logo bars still work?">. The second double quote closes the attribute early, the rest is broken JSX, build fails.
Bare comparison operators in prose. Less common, but x <- y in a sentence trips the same parser. Wrap code-y content in backticks and it bypasses JSX entirely.
The greps, from the audit script:
# Number-space after angle bracket
grep -nE '<[0-9]' src/content/writing/*.mdx && exit 1
# Number-space in percent context (catches "over <15 percent" style)
grep -nE '<[<>]?[0-9]+ (percent|%)' src/content/writing/*.mdx && exit 1
# Attribute value with inner double quotes
grep -nE '(q|label|title|attribution)="[^"]*"[^"]*"' src/content/writing/*.mdx && exit 1
The fix at the writer level is to use word forms for comparisons ("below 2 percent," "over 15 percent") and to escape inner quotes or switch to single quotes on the outer attribute (q='Do "As seen in" logo bars still work?').
If you skip this check: the build fails after dispatch, the error message points at the wrong line, and you spend twenty minutes hunting a character instead of shipping.
Check 3: diff the registry against the template examples
Every MDX component a writer references has to be registered in mdx-components.tsx. If a template example cites <AwesomeNewComponent> but nobody ever added it to the registry, MDX silently falls back to rendering it as a lowercase HTML tag. No error. No warning. Just a broken element in the tree.
The audit takes two greps:
# Extract registered components from mdx-components.tsx
grep -oE '^ [A-Z][A-Za-z0-9]+,' mdx-components.tsx | sed 's/[ ,]//g' | sort > /tmp/registered.txt
# Extract referenced components from the batch's MDX files
grep -rohE '<[A-Z][A-Za-z0-9]+' src/content/writing/*.mdx | sed 's/^<//' | sort -u > /tmp/referenced.txt
# Anything referenced but not registered is a silent render bug
comm -23 /tmp/referenced.txt /tmp/registered.txt
If the last command prints anything, you have components the MDX compiler will not resolve. The writer common-instructions file should also reference only registered components, so the source of pattern-matching stays clean.
If you skip this check: a writer invents a plausible component name and the MDX still compiles. Reader gets a blank div where your interactive demo should be.
Check 4: dry-run one MDX through the build
Static greps catch pattern bugs. They do not catch template-level issues or registration bugs in edge cases. Before you green-light the full parallel run, dispatch one writer for one representative topic, let it complete stages 0-8 end-to-end, and run next build against the result.
If the build fails, the problem is almost always either:
- A new component the template wanted that is not registered.
- A prop signature the writer pattern-matched wrong from a stale example.
- An MDX parse hazard your grep rules missed.
Fix the root cause, update the writer common-instructions file and the audit script, and then dispatch the rest. You pay for one sub-agent's tokens instead of sixty sub-agents' tokens.
For operators running this pattern regularly, the parallel versus sequential agent dispatch tradeoff frames when the fan-out is even worth the blast radius. The one-writer dry-run is a cheap hedge that preserves the parallel win while cutting the worst failure mode. The same discipline applies outside writer fleets: the cross-OS image generation pipeline walkthrough documents the same pre-flight gating for a Flux and Z-Image batch, where a bad prompt propagating across a hundred renders is the image-world equivalent of prop drift.
If you skip this check: the first time a template-level bug hits the batch, sixty files fail the build for the same reason. You debug once, but you wasted fifty-nine dispatches.
Check 5: automate all four in a pre-dispatch script
None of these checks are worth doing only sometimes. Each one has already cost me a batch. The audit script lives alongside the orchestrator, runs before the parallel dispatch call, and exits non-zero on any failing check.
#!/usr/bin/env bash
set -euo pipefail
BATCH_DIR="${1:-src/content/writing}"
# Check 1: prop drift on shared components
grep -l 'FaqItem question=' "$BATCH_DIR"/*.mdx && { echo "FaqItem uses q= not question="; exit 1; } || true
grep -l 'KeyTakeaways items=' "$BATCH_DIR"/*.mdx && { echo "KeyTakeaways uses children, not items="; exit 1; } || true
# Check 2: MDX parse hazards
grep -nE '<[0-9]' "$BATCH_DIR"/*.mdx && { echo "number-after-angle-bracket hazard"; exit 1; } || true
grep -nE '<[<>]?[0-9]+ (percent|%)' "$BATCH_DIR"/*.mdx && { echo "percent comparison hazard"; exit 1; } || true
grep -nE '(q|label|title|attribution)="[^"]*"[^"]*"' "$BATCH_DIR"/*.mdx && { echo "nested double-quote hazard"; exit 1; } || true
# Check 3: registry diff
grep -oE '^ [A-Z][A-Za-z0-9]+,' mdx-components.tsx | sed 's/[ ,]//g' | sort > /tmp/registered.txt
grep -rohE '<[A-Z][A-Za-z0-9]+' "$BATCH_DIR"/*.mdx | sed 's/^<//' | sort -u > /tmp/referenced.txt
MISSING=$(comm -23 /tmp/referenced.txt /tmp/registered.txt)
if [ -n "$MISSING" ]; then
echo "Unregistered components referenced:"
echo "$MISSING"
exit 1
fi
echo "Pre-dispatch audit passed."
Check 4 (the dry-run build) is a separate step because it needs the dev server or a full next build. I run it manually against the first completed writer output before the rest of the fleet kicks off. The rest is fully mechanical.
If you skip this check: you run checks 1-3 inconsistently, miss one, and the round that slips through is the expensive one.
Why this is a parallel-agent problem specifically
A single writer catches its own bugs in the stage 8 self-audit. Sixty parallel writers ship sixty files in eight minutes, and any bug in the first file becomes a bug in the next fifty-nine, because each fresh writer reads the earlier output to pattern-match structure and prop usage.
“A single writer catches its own bugs. Sixty parallel writers propagate the first writer's bug into fifty-nine more files before anyone looks.
”
That is the whole point of the pre-dispatch audit. The blast radius of a parallel fleet is the reason fan-out is fast, and it is also the reason any shared-component bug ships at scale. The audit catches the bug before the blast radius grows. The alternative is a human doing a fifty-nine-file post-hoc diff, which is slower than the original 8-minute batch.
For the broader pattern of what agents can and cannot catch for themselves, the agent failure modes piece covers the self-recovery patterns I rely on. The component-prop drift falls into the category where the agent cannot detect the failure at all, because the bug is downstream of the writer's own context window. When a writer is about to hit its own context ceiling, the session-state paste handoff pattern is how I preserve the audit checklist across the break, so the next session inherits the same pre-dispatch gate.
FAQ
Does this only matter for parallel dispatch?
The checks are cheap enough that I run them on every batch, sequential or parallel. The reason parallel dispatch gets the headline treatment is that the blast radius scales with fleet size. One writer hitting prop drift is a five-minute fix. Sixty writers inheriting it is a one-hour cleanup.
Can the writer agent audit its own output before returning?
Partially. Stage 8 of the writer pipeline runs an anonymization grep and an SEO check. It cannot reliably catch prop drift because the writer's own context does not include every other writer's output. The audit has to run at the orchestrator level, after the fleet returns.
What about a full TypeScript or MDX schema check instead of greps?
Fair question. A real schema check would be more robust. In practice the five greps catch 95 percent of the bugs in 15 seconds. A full TS check via next build catches the remaining parse errors and any registry gaps, which is why check 4 exists. Layering both beats picking one.
How do I teach the writer fleet to avoid prop drift in the first place?
A writer common-instructions file that lists every MDX component with a canonical example. The writer reads it before drafting. When you add a new component, you update that file in the same commit. The audit script then enforces it.
What happens when I add a brand-new MDX component to the site?
Three edits in one commit: register it in mdx-components.tsx, add the canonical example to the writer common-instructions file, and add a grep for the wrong-prop pattern to the audit script. That way the next parallel batch cannot reintroduce the drift.
For the full operating context around dispatching agent fleets safely, the Claude Code agent engineering handbook indexes the related decisions, the parallel reviewer pattern shows the dispatch shape these checks are gating, and the sub-agent decision log covers the upstream choice of whether to dispatch at all. Operators running this pattern daily use the Operator's Stack as the curriculum for the whole pipeline, and the same pre-flight discipline shows up in the client engagements under /work where a broken deliverable across a parallel ship is the difference between a clean invoice and a rescue week.
Sources and specifics
- Round-2 writer fleet on michaeldishmon.com shipped two silent rendering bugs: FaqItem prop drift across 14 articles, KeyTakeaways prop drift across 10 articles. Both were caught in a human deep-pass after the batch went live.
- Fix commits in this repo: e3a44ce (KeyTakeaways prop API correction) and 5356e33 (FaqItem attribute and editorial fix).
- The MDX parse hazards cited here reproduce in any MDX v2 or v3 compiler: number-after-angle-bracket is interpreted as a JSX tag open, and nested double quotes terminate the attribute early. The safe alternatives (word forms for comparisons, single-quoted outer attribute) were verified against the repo's current
next-mdx-remotepipeline on Next.js 16. - Audit script runtime benchmark: 15 seconds on a 60-article batch on an M1 Mac with warm disk cache.
- The FaqItem component signature is
{ q: string; children: ReactNode }; the KeyTakeaways component signature is{ title?: string; children: ReactNode }. Source:src/components/writing/mdx/faq-accordion.tsxandsrc/components/writing/mdx/key-takeaways.tsx.
