Skip to content

Agent-generated Markdown is hard-wrapped at a fixed column width #10

Description

@kelchm

Problem

Agents (Claude Code, grok, codex) consistently write Markdown hard-wrapped at a fixed column width — roughly 80 characters. This is wrong for essentially every Markdown target: READMEs, docs, PR bodies, design notes. The renderer already wraps, so a fixed column width produces ragged diffs (a one-word edit reflows an entire paragraph) and fights every viewport it's read in.

Fixed-width wrapping is correct for git commit messages and should stay there. Everything else should be soft-wrapped — one line per paragraph, breaks only where they carry meaning.

This has been a recurring, low-grade annoyance across many sessions rather than a single incident, which suggests the current approach isn't structurally sound.

Why the current setup doesn't hold

Findings from an investigation on 2026-08-16:

  • The rule exists but is weak. ~/.claude/CLAUDE.md says "When writing markdown, avoid the use of line breaks at a fixed column width". It's phrased as a prohibition with no replacement behavior, and it's filed under "Language-specific Guidance → Markdown" alongside genuinely niche items — but Markdown is produced in nearly every task, not occasionally.
  • Nothing enforces it. ~/.claude/settings.json has no hooks block at all. Compliance is entirely up to the model.
  • Delegated models never see the rule. This is likely the largest single gap:
    • ~/.grok/AGENTS.md does not exist. Grok scans ~/.grok/ for global rules (AGENTS.md / Claude.md / AGENT.md, per its README), so the file is simply absent — and grok-4.5 is the default implementer for bulk work.
    • ~/.codex/AGENTS.md exists but is 0 bytes.
  • Some output never touches disk. Markdown written directly into gh pr create --body, a heredoc, or a chat response bypasses any file-based tooling entirely.

Design space

Roughly four channels produce Markdown, and they don't all have the same fix available:

Channel File-based tooling can reach it?
Claude Code (main + subagents) yes
grok-4.5 yes, if hooked
codex / gpt-5.6 yes, if hooked
Inline (gh --body, heredocs, chat) no

Approaches worth evaluating, roughly independent of each other:

Instructions. Rewrite the rule positively (state what to do, not what to avoid), relocate it out of the language-specific section, and propagate it to ~/.grok/AGENTS.md and ~/.codex/AGENTS.md. Cheap and it's the only thing that reaches the inline channel — but it stays probabilistic, which is what we already know doesn't fully work.

Formatter on write. A PostToolUse hook on .md writes, shelling out to an existing formatter. All three mainstream options support unwrapping as a first-class mode:

  • Prettier — --prose-wrap never
  • dprint — markdown.textWrap: "never"
  • mdformat — --wrap no

Note that a stack choice is implied here. Prettier is not currently in use (only two vendored forks reference it), so adopting it means pulling Node tooling into repos that have none. dprint is a single Rust binary and is available via aqua:dprint/dprint, which fits the existing mise/aqua setup. Not a settled question — just a constraint worth being explicit about.

Repo-level formatting. Add the same formatter to per-project format/pre-commit steps rather than (or in addition to) an agent hook. Catches everything that lands in a repo regardless of who wrote it, but doesn't cover ad-hoc files or give feedback at write time.

Open questions

  • Enforce, or just instruct better? Is the instruction gap alone (especially the two missing/empty delegate files) enough to explain the problem? Worth trying that first and measuring before building machinery.
  • Warn or auto-fix? A hook that reports and makes the agent rewrite is safe but costs a turn. A hook that rewrites in place is deterministic but mutates files behind the agent, which can invalidate its cached view of a file it just wrote.
  • Should unwrapping be unconditional? Every off-the-shelf textWrap: never joins all paragraphs, including deliberate one-sentence-per-line (semantic linefeeds). Distinguishing those is possible — greedy-wrapped text is exactly detectable by checking whether each break was forced — but no existing tool does it, and custom tooling here is probably not worth the maintenance. Do we write semantic linefeeds anywhere we'd miss?
  • Global or per-project? A global hook is a uniform invariant that repos can't silently opt out of. Per-project config is more conventional and more discoverable to collaborators. These may not be mutually exclusive.
  • Scope of enforcement. .md only, or .mdx too? Any paths that should be exempt (vendored, generated, third-party)?
  • How do we know it worked? Some way to tell whether this actually stopped, rather than relying on noticing it again months later.

Non-goals

  • Changing commit message wrapping — 72 columns there stays.
  • Reformatting existing Markdown across repos as part of this (worth considering separately; it would produce large diffs).

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions