Repository navigation
Description
Goal: under the C locale (and POSIX) the launcher, setup.sh and install.sh take a repository URL or path, or a ref, that holds a letter past ASCII (/srv/café/openRepoShape.git) the way they take it under C.UTF-8, and still refuse every control character. One rule, #760's byte rule, in every place that asks, so the three scripts and the package agree.
Filed by lane openRepoShape-2 on Brett Heap's word ("file all", 2026-10-18T14:45:31Z).
- Source: w866, openRepoShape:
--versionand two fetch refusals print$OPENREPOSHAPE_REPOand$OPENREPOSHAPE_REFwith no check, so an escape or a bidi override in either reaches the terminal as typed, and a scaffold run sends its fetch with them #866's writer, found it and left it alone. rv872, lane 2's re-verification of openRepoShape:$OPENREPOSHAPE_REPOand$OPENREPOSHAPE_REFare checked once, before--versionanswers and before any fetch, so a control character or a character a terminal draws as another in either is refused, never printed or sent #872 (openRepoShape:--versionand two fetch refusals print$OPENREPOSHAPE_REPOand$OPENREPOSHAPE_REFwith no check, so an escape or a bidi override in either reaches the terminal as typed, and a scaffold run sends its fetch with them #866), measured it as SHOULD-2 and ruled it out of scope for openRepoShape:$OPENREPOSHAPE_REPOand$OPENREPOSHAPE_REFare checked once, before--versionanswers and before any fetch, so a control character or a character a terminal draws as another in either is refused, never printed or sent #872: "resolve it with a follow-up issue coveringchecked_repo,ref_character_faultand setup.sh's two guards". - Why it is not openRepoShape:
--versionand two fetch refusals print$OPENREPOSHAPE_REPOand$OPENREPOSHAPE_REFwith no check, so an escape or a bidi override in either reaches the terminal as typed, and a scaffold run sends its fetch with them #866's: openRepoShape:--versionand two fetch refusals print$OPENREPOSHAPE_REPOand$OPENREPOSHAPE_REFwith no check, so an escape or a bidi override in either reaches the terminal as typed, and a scaffold run sends its fetch with them #866 movedshape_checkout's check up front, whole, so every run but--helpnow meets the locale's[![:print:]]. The rule is the fault, and it sits in six places that have to move together (measured below: fixing the launcher alone only moves the refusal into setup.sh). - Why it is filed: the tracking issues' rule (Tracking: setup, scaffold, family and the shared helpers, creating projects and families #459 and Tracking: the test suite, CI and the docs #461, "Known limits", Brett Heap 2026-10-12) puts a new edge case on a checklist. His word above overrides it for this one.
Lands on: main at 50178305, the v1.1.1 release commit. Every path:line below is read there. None of the six open PRs touches openRepoShape, setup.sh, install.sh, src/openreposhape/ or their test files (the coordinator's check, 2026-10-18T14:48Z).
Lines
- The launcher's
checked_reporefuses a URL or path with a letter past ASCII underC, and says it "carries a control character".-
The line is
openRepoShape:811-813:-* | *[![:print:]]*) die "OPENREPOSHAPE_REPO is not a value this tool will put on a `git clone` command line: it starts with '-' or carries a control character. ...".refuse_bad_values(:996-1002) calls it for every run but--help(openRepoShape:--versionand two fetch refusals print$OPENREPOSHAPE_REPOand$OPENREPOSHAPE_REFwith no check, so an escape or a bidi override in either reaches the terminal as typed, and a scaffold run sends its fetch with them #866). UnderC,[![:print:]]matches each byte past 0x7F, soé(C3 A9) is "not printable". -
Measured (bash 5.2.21; a clean environment; a stub
ghandcurlfirst onPATH, both failing;OPENREPOSHAPE_REPOis.../café/openRepoShape.git, a real bare repository in a folder whose name holds the bytesC3 A9). Each run is the launcher at v1.1.0 (190133d5) and at50178305underC, at50178305underC.UTF-8, and at50178305with the rule below put in underC:Run v1.1.0, C50178305,C50178305,C.UTF-8rule moved, C--versionprints it, exit 0 exit 2 prints it, exit 0 prints it, exit 0 scaffold run Atlas --org acme2 requests, the fetch of setup.sh(the stub fails both), then exit 2exit 2, no request as v1.1.0 as v1.1.0 --installfrom a fileinstalled exit 2, nothing placed installed installed --doctor <dir>beside a checkoutthe doctor ran, exit 2 (not a shape root) exit 2, the doctor never ran the doctor ran the doctor ran --doctor <dir>, no checkout, a cloneexit 2, refused exit 2, refused the clone was made, the doctor ran the same --preflight,$OPENREPOSHAPE_SETUP_SHin a checkoutthe preflight ran exit 2 the preflight ran the preflight ran -
So openRepoShape:
$OPENREPOSHAPE_REPOand$OPENREPOSHAPE_REFare checked once, before--versionanswers and before any fetch, so a control character or a character a terminal draws as another in either is refused, never printed or sent #872 (in v1.1.1) made four runs refuse underCa value that v1.1.0 ran. At v1.1.0 only--doctor's clone refused it, and thesetup.sha scaffold run fetched (next line).
-
- The launcher's
ref_character_faultdoes the same to a ref.- The line is
openRepoShape:915-917:*[![:print:]]*) printf '%s' "$what carries a control character, which is not a branch, tag or commit this tool will pass to `git checkout`." ;;.refuse_bad_valuescalls it at:1000, andref_faultat:889. A ref with a letter past ASCII is no control character, and git takes one (git check-ref-format --branch caféexits 0, measured). - Measured with
OPENREPOSHAPE_REFset tocafé(63 61 66 C3 A9) and an ASCII local repository:- under
C,--version, a scaffold run,--install(with and withoutOPENREPOSHAPE_RECORD_REF=1) and--doctoreach exit 2 with$OPENREPOSHAPE_REF carries a control character, ..., and no request is sent; - under
C.UTF-8,--versionprints it (exit 0), a scaffold run sends 2 requests, and--installwithout the record installs; - under
C.UTF-8,--installwith the record and--doctorexit 2 withref_fault's shape refusal,$OPENREPOSHAPE_REF is 'café', which is not a branch, tag or commit ...: a ref starts with an ASCII letter or digit, uses only ASCII letters, ....
- under
- So for a ref the outcome differs for the runs that never ask the shape (
--version, a scaffold run,--installwithout the record), and the words are wrong for the rest.
- The line is
-
setup.sh's two guards refuse the same underC, and the launcher passes the value tosetup.shas it was typed.- Guard 1,
setup.sh:207-210:-*|*[![:print:]]*on$SHAPE_REPO, which is$OPENREPOSHAPE_REPO(or the default). The launcher exports the clone URL it made as$OPENREPOSHAPE_REPO(openRepoShape:1360), so a value the launcher passes still meets this guard. - Guard 2,
setup.sh:242-245:*[![:print:]]*on$SHAPE_REF, which is the--shape-refargument and not$OPENREPOSHAPE_REF. It runs after the length check and before the shapecase. - Both run only on the self-bootstrap path, after
is_shape_checkoutfound no checkout (:114-119).README.md:348-350'scurl ... setup.sh | bash -s --is that path with no launcher. - Measured with a
setup.shoutside any checkout,--preflight, a stubgh:OPENREPOSHAPE_REPO= thecafépath, underC: exit 2,OPENREPOSHAPE_REPO is not a value this tool will put on a `git clone` command line: it starts with '-' or carries a control character. .... UnderC.UTF-8: the clone was made and the preflight ran (exit 1 here, the stubghis not logged in).--shape-ref caféwith an ASCII repository, underC: exit 2,--shape-ref carries a control character, .... UnderC.UTF-8: exit 2,--shape-ref is 'café', which is not a branch, tag or commit ...: ... uses only ASCII letters .... Both refuse; only the words differ.- The launcher and
setup.shtogether,LC_ALL=C, thecafépath,--preflightwith a localsetup.shoutside any checkout: both as they are at50178305, exit 2 from the launcher; the launcher moved andsetup.shas it is, exit 2 fromsetup.sh:208; both moved, the clone was made and the preflight ran. So the launcher andsetup.shmove in one change.
- Guard 1,
-
install.shrefuses the same underC, in the same wrong words, and it has two more uses of the same class.checked_repo,install.sh:136-138:*[![:print:]]*before the owner/name checks. An owner/name on github.com is ASCII (:139-145refuse anything else), socaféis refused under both locales:$OPENREPOSHAPE_REPO carries a control character; it must be an owner/name ...underC,$OPENREPOSHAPE_REPO is '...', which is not an owner/name on github.com ...underC.UTF-8(measured, exit 2, no request, for thecafépath and foracme/café). Only the words differ.checked_version,:172-174(caféas the version:the version carries a control character ...underC, the shape refusal underC.UTF-8), and the option check,:453-455(-café:an option carries a control character ...underC,unknown option '-café' ...underC.UTF-8). All exit 2 with no request, in both locales.:226, which decides whether a redirect's location is named in a fault, is the same class and is left to the writer.
- The package's
openRepoShape --versionprints the same path underCwith exit 0, so the two--versioncommands disagree.- Measured (Python 3.12.3, a copy of the package outside any checkout,
python3 -m openreposhape --version): thecafépath printsopenRepoShape 0.0.0 (.../café/openRepoShape.git @ main), exit 0, underCandC.UTF-8. A lone0x9Bbyte prints as?, exit 0, under both. U+009B as UTF-8 (C2 9B) and an escape are refused, exit 2. - Its rule is
CONTROL_RE = [\x00-\x1f\x7f-\x9f]over the decoded text (src/openreposhape/standard.py:72, used at:190). That is install.sh and openRepoShape: a$OPENREPOSHAPE_BIN_DIRholding:or a newline is refused byinstall.shonly after its fetch, and one holding a carriage return or an escape is placed and echoed as typed, so both refuse every control character before they fetch or print anything #760's rule with the lone byte passing, and the launcher would then agree with it.
- Measured (Python 3.12.3, a copy of the package outside any checkout,
What is checked today
- The rule the launcher already holds elsewhere,
install_commandsfor$OPENREPOSHAPE_BIN_DIR(install.sh and openRepoShape: a$OPENREPOSHAPE_BIN_DIRholding:or a newline is refused byinstall.shonly after its fetch, and one holding a carriage return or an escape is placed and echoed as typed, so both refuse every control character before they fetch or print anything #760, install.sh and openRepoShape: the control-character check matches bytes, so a GBK or GB18030 name holding0xC2and a byte from0x80to0x9Fis refused as a control, and a lone0x9B, which an 8-bit terminal acts on, is placed #858),openRepoShape:639-646:seen="$dir" if (LC_ALL=C; case "$dir" in *$'\302'[$'\200'-$'\237']*) exit 0 ;; *) exit 1 ;; esac) || [ -n "$(LC_ALL=C; misdrawn_characters "$dir")" ]; then seen="$(locale_reading "$dir")" || seen="$dir" fi if (LC_ALL=C case "$dir" in *[[:cntrl:]]*) exit 0 ;; *) ;; esac case "$seen" in *$'\302'[$'\200'-$'\237']*) exit 0 ;; *) exit 1 ;; esac); then- Its comment says the other rule is the wrong one for
café(:605-608): "It is[[:cntrl:]]and not[![:print:]], which is what this file holds the repository and the ref to: a name with a letter the locale does not print, ascaféunder LC_ALL=C, is no fault of the directory's."install.sh:477-479says the same. install.shcopies the block (:501-510).setup.shhas none of it.
- Its comment says the other rule is the wrong one for
misdrawn_characters(install.sh and openRepoShape: a$OPENREPOSHAPE_BIN_DIRholding a bidi override, U+200B, U+2028 or U+FEFF passes #804's control-character check and is echoed as typed, so the PATH note can show a directory as another #837, install.sh and openRepoShape: a$OPENREPOSHAPE_BIN_DIR, the repository or a ref holding a bidi control, a character of no width, a soft hyphen or U+2028 is refused by its code point before any request and never echoed, so no line shows the value as another #841) runs first inchecked_repo(:808-810),ref_character_fault(:910-914) and install.sh's three checks. It builds each character's UTF-8 bytes and looks for them underLC_ALL=C, so the locale does not change it.- No test pins
caféunderC.test_version_prints_a_path_a_utf_8_locale_prints(tests/test_openreposhape_command.py:2399-2411) runsNOT_CONTROLSunderC.UTF-8only, and its docstring says: "UnderCthat class matches every byte past ASCII, so there the path is refused, as setup.sh's clone and the doctor's refuse it."tests/test_setup_sh.pyhas no locale case, and each of its two guards has one newline case (:877and:997). - Not checked by
setup.shat all: install.sh and openRepoShape: a$OPENREPOSHAPE_BIN_DIRholding a bidi override, U+200B, U+2028 or U+FEFF passes #804's control-character check and is echoed as typed, so the PATH note can show a directory as another #837's characters. Measured underC.UTF-8, asetup.shoutside a checkout takes U+202E inOPENREPOSHAPE_REPOpast guard 1, and git then prints it (fatal: repository '.../openRepoShape.git, the bytesE2 80 AE, thenx' does not exist);--shape-ref v1U+202Expasses guard 2 and the shape refusal prints it as typed. UnderCboth are refused today, by the same[![:print:]]. The launcher refuses them first when it is the way in.
The fix, and what it costs
-
The rule: install.sh and openRepoShape: a
$OPENREPOSHAPE_BIN_DIRholding:or a newline is refused byinstall.shonly after its fetch, and one holding a carriage return or an escape is placed and echoed as typed, so both refuse every control character before they fetch or print anything #760's, asinstall_commandsholds it, withoutlocale_reading: underLC_ALL=C,*[[:cntrl:]]*or*$'\302'[$'\200'-$'\237']*. Forchecked_repothe leading-stays:-* | *[[:cntrl:]]* | *$'\302'[$'\200'-$'\237']*. Each place becomesif (LC_ALL=C; case "$value" in ... ) exit 0 ;; *) exit 1 ;; esac); then die ...; fi, three lines, as thecaseit replaces is. -
Probed by putting it in the launcher's two places,
setup.sh's two andinstall.sh's three in a scratch copy, then--versionwith each value in the repository and, where it applies, the ref (bash 5.2.21).UNPRINTABLE_VALUES(tests/test_openreposhape_command.py:2245) has 12 values: the 9 rv872 probed and 3 trailing-newline forms added since.Value now, Cnow, C.UTF-8rule moved, both éin a path or a refrefused passes passes the 12 values: for the repository an escape, U+009B, U+202E, U+200B, a leading -and three trailing-newline forms, for the ref the first fourrefused refused refused DEL, TAB, U+0085, U+0080, U+009F refused refused refused a lone E9byte then U+009Brefused refused refused a lone 9Bor85byte (not UTF-8)refused refused passes a lone E9byte (Latin-1café)refused refused passes U+00A0, U+200C, U+1F600, U+0301, U+E000 refused passes passes U+0378 (unassigned), U+FFFE (a noncharacter) refused refused passes - The ref column gave the same answers for the 11 values it was run on.
misdrawn_charactersstill covers U+202E and U+200B underC: all 41 code points ofMISDRAWN_CHARACTERSare refused by their code point, in the repository and the ref, underCandC.UTF-8, before and after (measured).
-
Existing tests: with the rule in all seven places,
tests/test_openreposhape_command.py,tests/test_setup_sh.pyandtests/test_install_sh.pygive 842 passed (563 s, at a load average of 40 to 58). Nothing fails, because nothing pins the old behavior or words underC. -
Mutants against those tests, one arm dropped from one place (rule moved, scratch copy):
- killed: the launcher's five (
checked_repowithout the C1 arm, without the control arm, without the leading-;ref_character_faultwithout either arm),setup.shguard 1 without the control arm, guard 2 without the control arm,install.sh'schecked_versionand option check without the control arm; - survive:
setup.shguard 1 without the C1 arm and without the leading-, guard 2 without the C1 arm, andinstall.sh'schecked_repowithout either arm,checked_versionwithout the C1 arm, the option check without the C1 arm. Sosetup.shhas no C1 case and no leading--case for its repository,install.shhas no C1 case, andinstall.sh'schecked_repohas no control-character case at all: measured with its control arm gone, the refusal at:141prints an escape in$OPENREPOSHAPE_REPOas typed.
- killed: the launcher's five (
-
Who can meet it: a real person can, and few will.
- The launcher documents
$OPENREPOSHAPE_REPOas "owner/name to fetch from" (openRepoShape:125,docs/cli.md:162), and an owner/name on github.com is ASCII. - The URL and path forms are
setup.sh's clone (docs/cli.md:118, "a fork or mirror in self-bootstrap mode") and the package's (src/openreposhape/cli.py:80, "owner/name, URL or path to clone"). - So it takes a mirror at a URL or path with a letter past ASCII (
/srv/café/..., a home folder namedjosé) and a shell atCor POSIX: cron, a CI step, a minimal container,env -i, asudothat resets the locale, orLANG=Cset on purpose. An owner/name is never met by it, which is whyinstall.shis wording only.
- The launcher documents
-
The lone
0x9Btrade, and the decision. The rule lets a lone9Bbyte (not UTF-8) through in both locales, where[![:print:]]refuses it. install.sh and openRepoShape: a$OPENREPOSHAPE_BIN_DIRthe bytes refuse is read again in the locale's own charset, so a GB18030, GBK or Shift_JIS name that holds no control character and nothing drawn as another is placed, and where nothing can read it the bytes decide as before #867's comment states the same trade for the bin dir,openRepoShape:632-634: "A lone C1 byte that is not UTF-8 (0x9B) is placed, as before: a terminal that reads UTF-8 draws it as a character it cannot read, and only one set to an 8-bit charset acts on it."- The package already makes it: its
--versionprinted a lone9Bas?, exit 0 (above). - No locale-free pattern keeps the refusal. The class
$'\200'-$'\237'over bytes also matches the second byte of U+00C0 (C3 80): measured underLC_ALL=C, it matchesaC3 80b, and the C1 pair pattern does not. - Decision: take the trade, as install.sh and openRepoShape: a
$OPENREPOSHAPE_BIN_DIRthe bytes refuse is read again in the locale's own charset, so a GB18030, GBK or Shift_JIS name that holds no control character and nothing drawn as another is placed, and where nothing can read it the bytes decide as before #867 did for the bin dir, so the launcher,setup.sh,install.shand the package give one answer. The other way, leaving a locale-keyed rule ([![:print:]]under UTF-8, bytes underC), keeps today's two answers. - A second cost, not measured in a locale: GBK, GB18030 and CP936 read each of
C2 80toC2 9Fas one hanzi (32 of 32 with Python's codecs; no such locale is installed here). Under such a locale[![:print:]]passes a path holding one, and the byte rule refuses it as a C1 control. install.sh and openRepoShape: the control-character check matches bytes, so a GBK or GB18030 name holding0xC2and a byte from0x80to0x9Fis refused as a control, and a lone0x9B, which an 8-bit terminal acts on, is placed #858 asked the locale again for$OPENREPOSHAPE_BIN_DIR(locale_reading). This issue does not:setup.shhas no copy of it, and the same locale already refuses a hanzi made ofC2 ADthroughmisdrawn_characters, whichopenRepoShape:543-551describes (install.sh and openRepoShape: the control-character check matches bytes, so a GBK or GB18030 name holding0xC2and a byte from0x80to0x9Fis refused as a control, and a lone0x9B, which an 8-bit terminal acts on, is placed #858's ground, unchanged).
- The package already makes it: its
Direction
- Move together, in one change:
checked_repo(openRepoShape:811-813),ref_character_fault(:915-917),setup.sh's guards (:207-210,:242-245), andinstall.sh'schecked_repo(:136-138),checked_version(:172-174) and option check (:453-455), to the rule above.misdrawn_charactersstays first where it is. No refusal's words change: "carries a control character" is then true of what it names. - Keep
setup.shas it is otherwise. It gets nomisdrawn_charactershere: the rule putsCwhereC.UTF-8is (above). Whethersetup.shshould refuse install.sh and openRepoShape: a$OPENREPOSHAPE_BIN_DIRholding a bidi override, U+200B, U+2028 or U+FEFF passes #804's control-character check and is echoed as typed, so the PATH note can show a directory as another #837's characters is the writer's call to note under "Not changed", or to file. - Reword what says the repository and ref are held to
[![:print:]]:openRepoShape:605-608,:797-801and:866-871,install.sh:128-130and:477-479, and the docstring attests/test_openreposhape_command.py:2401-2406. - Decide the lone
9Bas above, and write it into the comment where the rule is, as:632-634does.
Verify
tests/test_openreposhape_command.py,tests/test_setup_sh.pyandtests/test_install_sh.pypass on Linux.- New cases, one table in
tests/conftest.pyread by all three files, as setup.sh, install.sh and the openRepoShape command: each keeps its own copy of the old ref rule (*..* | *.lock), so a ref git refuses part by part (a.lock/b,a/.b,a//b) clones the standard before it fails #874'sREF_PARTSis, so the three agree:café(a path, a URL and an scp address for the repository, and a ref) underCandC.UTF-8, in each place: the launcher's--version, a scaffold run (the fakeghandcurlare asked, as underC.UTF-8),--installfrom a file,--doctor(a fakegitsees the clone) and--preflight;setup.sh's two guards;install.sh's three checks (refused, in the wordsC.UTF-8gives);- every control still refused in both locales, in each place: an escape, U+009B, DEL, TAB, U+0085, U+0080, U+009F and a trailing newline, and a leading
-for both repository guards.setup.shandinstall.shhold none of these but a newline today; - U+202E and U+200B refused by code point under
C; - the lone
9Band the loneE9pass in both locales, which pins the decision; - a lone
E9then U+009B is refused, asC1_BIN_DIRShas it.
- Mutants: each arm of the rule dropped from each place, as above: all killed, the seven that survive today included.
- A clean pair still prints
openRepoShape (opensoft/openRepoShape @ main).
Overlaps
- openRepoShape:
--doctor'sshape_checkoutrunsgit clonebeforechecked_ref, so a ref it refuses (a.lock/b,a//b, one over 255 characters) costs a whole clone first, and where the clone fails the ref's refusal is never reached #942, filed beside this one forshape_checkout(the clone beforechecked_ref,openRepoShape:829-848), is in the same file andtests/test_openreposhape_command.py, at other lines. After this change a refcaféunderCis held as it is underC.UTF-8:--doctorreaches the clone andchecked_refrefuses it after. - openreposhape package:
openRepoShape --versionprints a repository holding U+202E or U+200B as typed and exits 0, and a ref holding either is refused with the value as typed, where the launcher refuses both before it prints (#866) #939, filed beside this one for the package's--version(U+202E and U+200B printed as typed), is insrc/openreposhape/cli.py, which this issue does not touch. - No other open PR touches these files (checked at 2026-10-18T14:48Z).
Parent: #459: the launcher's own items went there, such as #649's machine: block and drive-letter lines, and setup.sh and install.sh are its subject.
Milestone: v1.2.0. v1.1.1 is released, and lane 3 places issues. It needs a value a person set in their own environment: a URL or path with a letter past ASCII, in a shell at C or POSIX. It is a change from v1.1.0 (above), but the usual owner/name is untouched, the outcome is right for every control character, and --doctor's clone and setup.sh refused this value long before. #872 (lane 2's, in v1.1.1) made the change, so lane 3 may place this as a v1.1.x back-port (#850) instead.
🤖 Generated with Claude Code
Activity
docs/release-notes/v1.1.1.md: a known limit names #940, the refusal u…
brettheap commentedon Oct 18, 2026
CLAIMED — lane openRepoShape-2, session 81650e29-c906-4fe8-96c9-55580bfe0c44@Eagle, 2026-10-18T17:38:56Z, for #940
Logged in
lanes/log/openRepoShape-2.mdatd252426f0b31dca8fd2e1cc5e23abc65741fa344.The three reads (lane-collision-protocol Rule 1):
docs/release-notes/v1.1.1.md: the points RV883 round 5 held for after…
openRepoShape, setup.sh and install.sh: a URL, path or ref with a let…
install.sh, openRepoShape, setup.sh: rv986's round 1 for #940 — a /re…
setup-project.py: the self-bootstrap refuses an `$OPENREPOSHAPE_REPO`…