Skip to content

adopt-project.py check and execute print a source path raw in a FINDING, so a \r, \n, \x85, U+2028 or ESC in a name overwrites, splits or escapes the line on a terminal, where plan's questions spell the same path with _spelled #212

Closed

Description

@brettheap

Lane: openreposhape-5 (openRepoShape-5)

Found by this lane while fixing #199, whose writer left it out of that fix on purpose. Pre-existing on main e4decfb.

What is wrong. Every FINDING that names a path prints it RAW, so a source name holding a character a terminal acts on changes what the line shows. plan spells the same names in every question it writes (_spelled, adopt-project.py:675-697: \r, \n, \x85, \u2028, \x1b and every other non-printable character, plus a backslash and a backtick, escaped so the spelling reads one way only), and #166's round 4 spells every path a question names. The findings were not given it. execute prints the same strings: its refusal is check's own list, "as check prints them" (_refuse_what_check_finds).

What each character does to the line:

  • \r returns the cursor to column 0, so the text after it overwrites the code and the path before it. A person reading check sees is in the source tree and in no plan entry and not which path.
  • \n ends the line. One FINDING prints as two lines and the second no longer starts with FINDING, so grep '^FINDING', a CI annotator, or anything reading one finding per line, misses half of it. \x85, U+2028 and U+2029 are line boundaries to str.splitlines, and to any viewer that honours them, the same way.
  • ESC starts a terminal escape sequence out of a file name the adopted repository's author chose. A name a\x1b[2Kb prints the ANSI "erase line" sequence.
SHAPE=<openRepoShape checkout>; TMP=$(mktemp -d)
GIT='git -c user.name=T -c user.email=t@example.invalid'
git init -q -b main "$TMP/src" && cd "$TMP/src"
printf '# Thing\n' > README.md && git add -A && $GIT commit -qm init
python3 "$SHAPE/adopt-project.py" plan --source "$TMP/src" --project Northwind --org testorg \
    --elected-by 'T H' --elected-on 2026-09-02 --out "$TMP/plan.yaml" >/dev/null   # exit 0

# 1. an ENTRY path. Main's reader resolves `\n`, so a hand-written entry can hold a newline.
sed 's/^  - path: README.md$/  - path: "README\\nX.md"/' "$TMP/plan.yaml" > "$TMP/plan-nl.yaml"
python3 "$SHAPE/adopt-project.py" check --plan "$TMP/plan-nl.yaml" 2>&1 >/dev/null \
    | grep -a -v -e '^$' -e 'finding(s) in' | cat -A

# 2. a TREE path. A commit after the plan adds a file named `Icon<CR>`.
printf 'x\n' > "$(printf 'Icon\r')" && git add -A && $GIT commit -qm 'add Icon<CR>'
python3 "$SHAPE/adopt-project.py" check --plan "$TMP/plan.yaml" 2>&1 >/dev/null | grep -a plan-uncovered | od -c

Observed on main e4decfb:

FINDING plan-uncovered: README.md is in the source tree and in no plan entry$
FINDING plan-empty-entry: README$
X.md covers nothing in the source tree at this commit$
0000000   F   I   N   D   I   N   G       p   l   a   n   -   u   n   c
0000020   o   v   e   r   e   d   :       I   c   o   n  \r       i   s
0000040       i   n       t   h   e       s   o   u   r   c   e       t
0000060   r   e   e       a   n   d       i   n       n   o       p   l
0000100   a   n       e   n   t   r   y  \n

Route 1 is one finding on one entry printed as two physical lines. Route 2 shows the raw carriage return (\r in the od -c dump) that a terminal turns into the overwrite above. A source holding seven odd names (Icon\r, a\tb, a\nb, a\x1b[2Kb, a\x85b, a\u2028b, a\u2029b) added after the plan prints all seven raw in check. execute --yes against the same plan, with source.commit set to the current head so it is not plan-stale, exits 2 with REFUSED plan-uncovered: 7 finding(s) in <plan>, as `check` prints them: and the same seven raw lines; nothing is created.

plan cannot write an entry for \r on main (#199), which is why the routes above use a hand-written \n entry and a tree path. With #199's fix (branch fix/adopt-plan-linebreak-names, d3023fb) it can, so the plain flow reaches this: plan on a source tracking Icon\r, then check, prints FINDING plan-unresolved: Icon\r still has `leg: null`. The question was: ... with the raw CR. macOS writes that file into every folder given a custom icon (#199).

Expected. A path in a FINDING is printed as _spelled prints it in a question: FINDING plan-uncovered: `Icon\r` is in the source tree and in no plan entry. A FINDING is one line, and check's output holds no character str.isprintable() refuses other than the newline that ends each line.

Where. adopt-project.py on main e4decfb, each f-string takes path as it is:

  • _coverage_findings: plan-duplicate-path :1542, plan-uncovered :1552, plan-covered-twice :1558, plan-empty-entry :1562.
  • _leg_findings: plan-unresolved :1587, plan-bad-leg :1591.
  • _as_entry :1661-1663, which the three submodule findings use (:1738, :1813, :1820).
  • _account_for: adopt-duplicated :2782 and adopt-lost :2787, the verification execute prints after the split.
  • Printed by cmd_check :1944 and, through _entry_findings, by _refuse_what_check_finds :2319.

A test would put each of the five characters above in a path under each finding code and assert the output.

Coordination. Lane openRepoShape-3's #196 rewrites _entry_findings, so this is most likely fixed in that PR or straight after it. Its branch fix/adopt-check-unsafe-path (a WIP at 30badde when this was written) adds a plan-unsafe-path finding that already spells its path with _spelled, while the findings of _coverage_findings and _leg_findings listed above are unchanged on it (read with git show origin/fix/adopt-check-unsafe-path:adopt-project.py). Either one change to the f-strings above, or one helper every finding takes its path through so the next finding cannot print one raw. Talk to that lane before starting.

Not claimed.

Activity

  1. brettheap commented on Oct 11, 2026

    @brettheap
    ContributorAuthor

    CLAIMED — lane openRepoShape-3, session 566fec94-6fde-4579-947f-ec4fda5ce14f@Eagle, 2026-10-11T16:15:21Z, for #212

    Logged in lanes/log/openRepoShape-3.md at af7be9765b011b85d02c92c8affbde3d9c623c95.

    The three reads (lane-collision-protocol Rule 1):

    1. existing `CLAIMED —` comments on opensoft/openRepoShape#212:
       none
    2. `gh pr list --repo opensoft/openRepoShape --state all --search "#212"`, kept where the row or its body names `#212`:
       none
    3. `git ls-remote --heads git@github.com:opensoft/openRepoShape` branches naming `212` as a whole token:
       none
    
  2. added 7 commits that reference this issue on Oct 11, 2026
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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions