fix(template): stop chmodding scripts the template does not own - #347
Open
ichoosetoaccept wants to merge 2 commits into
Open
fix(template): stop chmodding scripts the template does not own#347ichoosetoaccept wants to merge 2 commits into
ichoosetoaccept wants to merge 2 commits into
Conversation
Member
Author
|
This change is part of the following stack: Change managed by git-spice. |
This was referenced Aug 5, 2026
ichoosetoaccept
added a commit
that referenced
this pull request
Aug 5, 2026
The guard filtered raw lines that start with `- ` and contain `chmod`, which is
blind to the mapping and folded forms of a copier task:
_tasks:
- command: >-
chmod +x scripts/*.py
Copier runs that; the filter sees no `chmod` on the `- ` line and passes. Not
hypothetical syntax either -- copier.yml already uses `- command:` with `when:`
for two other tasks, so the next chmod could arrive in exactly the shape the
guard cannot see.
Parses `_tasks` and reads the command out of both shapes instead. Confirmed to
discriminate: with a folded chmod task injected, the old line filter matches
zero lines and the new assertion fails.
Reported by Greptile on #347.
ichoosetoaccept
added a commit
that referenced
this pull request
Aug 5, 2026
The guard filtered raw lines that start with `- ` and contain `chmod`, which is
blind to the mapping and folded forms of a copier task:
_tasks:
- command: >-
chmod +x scripts/*.py
Copier runs that; the filter sees no `chmod` on the `- ` line and passes. Not
hypothetical syntax either -- copier.yml already uses `- command:` with `when:`
for two other tasks, so the next chmod could arrive in exactly the shape the
guard cannot see.
Parses `_tasks` and reads the command out of both shapes instead. Confirmed to
discriminate: with a folded chmod task injected, the old line filter matches
zero lines and the new assertion fails.
Reported by Greptile on #347.
ichoosetoaccept
force-pushed
the
no-blanket-chmod-scripts
branch
from
August 5, 2026 23:34
b61c3ad to
2dbd7aa
Compare
`_tasks` opened with `chmod +x scripts/*.sh scripts/*.py`, which flipped the
mode of every file a downstream project keeps in `scripts/`, not just the one
the template ships. Nine shebang-less files went 644 -> 755 on a routine
0.41.10 -> 0.41.12 update, and ruff's EXE002 ("file is executable but no shebang
is present") took that repo from lint-clean to nine errors. No `.rej`, no
warning.
The obvious recovery is a trap too: `chmod 644 scripts/*.py` clears EXE002 and
raises EXE001 on the scripts that legitimately have shebangs. The real recovery
is `git checkout -- scripts/`, which nothing points you at.
Deleted rather than narrowed, because the task was never needed. Copier's
`_render_file` already chmods each rendered file to the template's mode,
preferring the template's *git-index* exec bit over `stat().st_mode` so a bit
committed by the template author survives filesystems that do not carry one
(Windows), then syncs the destination repo's index to match. It runs on copy and
update alike, and only for files copier actually renders -- so a downstream
`scripts/probe.py`, which the template does not own, is never a candidate.
Confirmed in copier 9.17.1 (`_main.py:862-907`) and empirically: rendering with
`--skip-tasks`, which never ran the chmod, already produces
`check-template-update.sh` at 755.
That makes the template's committed file mode load-bearing, so both halves are
pinned. One test asserts there is no chmod task in copier.yml; the other asserts
the shipped script *renders* executable. Without the second, committing that
file 644 would silently break `check-shebang-scripts-are-executable` on the
first commit of every generated project.
Closes DOT-628
The guard filtered raw lines that start with `- ` and contain `chmod`, which is
blind to the mapping and folded forms of a copier task:
_tasks:
- command: >-
chmod +x scripts/*.py
Copier runs that; the filter sees no `chmod` on the `- ` line and passes. Not
hypothetical syntax either -- copier.yml already uses `- command:` with `when:`
for two other tasks, so the next chmod could arrive in exactly the shape the
guard cannot see.
Parses `_tasks` and reads the command out of both shapes instead. Confirmed to
discriminate: with a folded chmod task injected, the old line filter matches
zero lines and the new assertion fails.
Reported by Greptile on #347.
ichoosetoaccept
force-pushed
the
no-blanket-chmod-scripts
branch
from
August 6, 2026 06:52
2dbd7aa to
15c02ed
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Deletes the
chmod +x scripts/*.sh scripts/*.pytask fromcopier.yml's_tasks. Adds two tests pinning both halves of why that is safe.Why
The task chmodded every file a downstream project keeps in
scripts/, not just the one the template ships. Nine shebang-less files went644 -> 755on a routine 0.41.10 → 0.41.12 update, and ruff'sEXE002("file is executable but no shebang is present") took that repo from lint-clean to nine errors — with no.rejand no warning.The obvious recovery is a trap of its own:
chmod 644 scripts/*.pyclearsEXE002and raisesEXE001on the scripts that legitimately have shebangs. The real recovery isgit checkout -- scripts/, which nothing points you at.Deleted rather than narrowed, because the task was never needed. Copier's
_render_filealready chmods each rendered file to the template's mode — preferring the template's git-index exec bit overstat().st_mode, so a bit committed by the template author survives filesystems that do not carry one (Windows) — then syncs the destination repo's index to match. It runs oncopyandupdatealike, and only for files copier actually renders, so a downstreamscripts/probe.pyis never a candidate.Confirmed two ways: in copier 9.17.1's source (
_main.py:862-907), and empirically — rendering with--skip-tasks, which never ran the chmod, already producescheck-template-update.shat755.That makes the template's committed file mode load-bearing, which is why there are two tests rather than one. Without
test_shipped_script_is_rendered_executable, committing that file644would silently breakcheck-shebang-scripts-are-executableon the first commit of every generated project.Test plan
test_tasks_do_not_chmod_downstream_scripts— asserts nochmodtask incopier.yml.test_shipped_script_is_rendered_executable— asserts the renderedscripts/check-template-update.shhas the exec bit, pinning the copier behaviour now doing the work.202 passed.Known gap: no test exercises
_tasksend-to-end — every copier invocation in the suite passes--skip-tasks, since running tasks meansuv sync --upgrade(network, minutes). The first assertion is therefore structural oncopier.ymltext; the behavioural claim is covered by the second test plus the source reading above.Closes DOT-628
Greptile Summary
This change removes the blanket
chmodtask and strengthens the regression check by parsing Copier task definitions as YAML.The previously observed script-mode regression was exercised and disproved for this revision: a real Copier update leaves a downstream-owned shebang-less Python script at mode
0644, while the template-ownedscripts/check-template-update.shremains executable at mode0755.Confidence Score: 5/5
Safe to merge; no blocking failure remains.
A real Copier copy/update flow confirmed that downstream-owned scripts keep their mode and the template-owned shell script remains executable.
What T-Rex did
Reviews (4): Last reviewed commit: "test: parse copier.yml as YAML in the ch..." | Re-trigger Greptile