Skip to content

A marker repeated after a section pair makes generation rewrite the user's prose #691

Description

@Jamie-BitFlight

What happens

ReadmeEditor pairs the last start marker in the document with the last end marker in the document. Neither is paired relative to the other. A README that repeats either marker after a real pair — in a fenced example, say — therefore has the wrong two markers paired, and the text between them rewritten.

Two shapes, both reproduced against the built CLI:

  • A repeated end marker widens the span. Everything between the real end marker and the repeat is replaced by the generated content. User prose is deleted.
  • A repeated start marker moves the start. The generated content lands at the repeat, and the tail of the README between the real pair and the repeat is duplicated.

Either way the tool rewrites content outside a marker pair, which docs/tool-contract.md calls a breaking change: content outside a pair is the user's.

Where

src/helpers.ts:

export function indexOfRegex(str: string, providedRegex: RegExp): number {
  // ... walks every match and keeps the last one
}

The name says first; the body returns the last. ReadmeEditor.getTokenIndexes uses it for the end marker and lastIndexOfRegex for the start marker, so both ends of the span come from the last match anywhere in the file.

The (^|[^\])` guard does not help here. It excludes a marker quoted inline between backticks or escaped with a backslash. A marker alone on its own line inside a fence is preceded by a newline, so it matches like any other.

Reproduction

action.yml:

name: Decoy Action
description: An action whose README documents the markers
inputs:
  token:
    description: A token
    required: true
runs:
  using: node20
  main: index.js

README.md, repeated end marker:

# Decoy

<!-- start inputs -->
| old | table |
|---|---|
<!-- end inputs -->

## USER PROSE THE TOOL MUST NOT TOUCH

* a bullet the user wrote

Closing marker looks like:

```text
<!-- end inputs -->
```

Run the generator. The heading, the bullet, the sentence and the fence opener are gone: the span now runs from the real start marker to the repeat inside the fence.

Swap that fenced line for <!-- start inputs --> and the mirror shape appears instead — the table, the end marker, the prose and the fence opener are all emitted twice.

Workarounds that do work

Both are tested against the built CLI and both leave the file correct:

  • Put marker examples above the generated sections rather than below.
  • Quote the markers inline between backticks, which the guard excludes.

This repository's own README uses the first form.

Why this is filed separately from #668

#668 is about formatter scope, and the fix for it (PR #690) leaves this untouched: the pairing rule is in getTokenIndexes, not in the formatter. PR #690's outside-content check in scripts/verify-readme-contract.mjs does catch both shapes, so the integration workflow reports them against a third-party repository rather than staying silent. That check deliberately does not reuse getTokenIndexes — a mask built on the editor's own pairing would hide the very bytes a mis-paired span destroyed.

Impact

This tool runs against arbitrary third-party repositories, and a README that documents the markers is an ordinary shape for a repository that uses this tool — the marker names are what a user copies from README.example.md. #644 covers markers that are absent; this covers markers that are present and paired wrongly.

Suggested direction

Pair the last start marker with the first end marker after it. Rename indexOfRegex to say what it returns, or give it a first-match sibling, so the next caller does not inherit the same surprise. Decide what to do when a section's markers are ambiguous rather than guessing silently — #644 is the neighbouring question.

Activity

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

Metadata

Metadata

Labels

releasedThis issue/pull request has been released.

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions