The problem
Every line-based updater matches its regexp against a line's whole text, and none of them knows where the line's
comment begins. A reference someone commented out therefore reads to Update-time exactly like a live one: it is
resolved, a request is spent on it, and it is rewritten inside the comment.
Probed 2026-08-28, matching each updater's own regexp against a line of the kind of file it scans:
YAML image, commented out: MATCHES 'image: redis:6.2'
devcontainer image in a // comment: MATCHES '"image": "mcr.microsoft.com/devcontainers/base:1.0"'
devcontainer feature in a // comment: MATCHES '"ghcr.io/devcontainers/features/docker-in-docker:2": {'
action uses: in a comment: MATCHES 'uses: actions/checkout@v4'
pre-commit repo: in a comment: MATCHES 'repo: https://github.com/astral-sh/ruff-pre-commit'
jsDelivr URL in a # comment: MATCHES 'https://cdn.jsdelivr.net/npm/clipboard@2.0.11/dist/clipboard.min.js'
A commented-out line is everyday practice: the image a Compose file ran before an upgrade, the alternatives a
devcontainer template ships, a workflow step dropped for now.
The pre-commit config is the serious one
There the mistake does not stay in the comment. _updated_lines reads the repository from any line _REPO_RE
matches and applies it to every rev: below it (update_pre_commit_config.py:70-77), so a comment naming another
project silently re-points a live hook:
repos:
- repo: https://github.com/psf/black
# We also considered repo: https://github.com/astral-sh/ruff-pre-commit
rev: v24.1.0
Black's rev: is then resolved against astral-sh/ruff-pre-commit and pinned to that project's commit. Nothing
about the result looks wrong to a reader, and the log names the wrong repository.
Stripping the comment is not the fix
Two updaters read a version out of a trailing comment. Probed the same day:
a pinned action, version in its trailing comment: matches 'uses: actions/checkout@<sha> # v4.1.1'
version read from the comment: '4.1.1'
a frozen rev, version in its trailing comment: matches 'rev: <sha> # frozen: v4.5.0'
version read from the comment: 'v4.5.0'
An updater that matched only the code part of a line would stop reading every pinned action and every frozen
rev: in the repository.
The rule that holds is narrower: a reference may not start inside a comment. What follows the reference stays
readable, so the trailing version comments keep working.
Scope
- Say where a line's comment begins: a comment lead that opens the line or follows whitespace, outside quotes. The
leads are the ones domain/marker.py already spells, so they are named once. Quoting matters: a repo: value
holds https://, which is not a comment.
- Reject a match that starts at or after that point, in the three places that match a line: the shared engine's
_reference_match, the pre-commit pass, and the jsDelivr pass. The last two walk their own lines.
- Leave marker reading alone. An
# update-time: marker is a comment, and parse_marker goes on reading the
whole line.
Out of scope
Four defects the same audit found, each with a cause of its own:
YAML_IMAGE_REFERENCE matches any key ending in image, so init_image: busybox:1.36 is taken for an image
reference (sources/oci.py:69).
_ATTRIBUTES_RE gives a resolved integrity hash to the next line holding a {, so a bare-string jsDelivr URL
writes "integrity" into whatever dictionary follows it (updaters/update_jsdelivr.py:44).
_IMAGE_RE matches a line-initial FROM inside a BuildKit heredoc body, so embedded SQL is looked up as an
image (updaters/update_dockerfile_base_image.py:21).
_STAGE_NAME_RE is greedy and case-insensitive, so a trailing comment's "as" is captured as the stage name and
the real stage goes unregistered (updaters/update_dockerfile_base_image.py:27).
A uses: inside a run: shell string is out of scope too. It sits in a string rather than a comment, so the rule
above does not reach it.
Documentation
- A changelog entry: a project with a commented-out reference has been having it rewritten, so the fix is
user-visible.
- No README change. The README never says what happens to a commented-out reference, and the fix makes it do the
thing a reader would assume.
Open questions
- Does the comment offset belong on
Line, which already carries the text a marker is read from, or in
primitives/text.py beside line_number and column?
- How far does the quote tracking need to go? A YAML single-quoted scalar and a Python string are the cases the
scanned files hold; a Python triple-quoted string spans lines, which a line-based pass cannot see.
- Should a marker inside a commented-out reference's own line still be read, as in
# image: redis:6.2 # update-time: ignore? The reference is not updated either way, so the question is only
whether such a marker is reported as recognised.
The problem
Every line-based updater matches its regexp against a line's whole text, and none of them knows where the line's
comment begins. A reference someone commented out therefore reads to Update-time exactly like a live one: it is
resolved, a request is spent on it, and it is rewritten inside the comment.
Probed 2026-08-28, matching each updater's own regexp against a line of the kind of file it scans:
A commented-out line is everyday practice: the image a Compose file ran before an upgrade, the alternatives a
devcontainer template ships, a workflow step dropped for now.
The pre-commit config is the serious one
There the mistake does not stay in the comment.
_updated_linesreads the repository from any line_REPO_REmatches and applies it to every
rev:below it (update_pre_commit_config.py:70-77), so a comment naming anotherproject silently re-points a live hook:
Black's
rev:is then resolved againstastral-sh/ruff-pre-commitand pinned to that project's commit. Nothingabout the result looks wrong to a reader, and the log names the wrong repository.
Stripping the comment is not the fix
Two updaters read a version out of a trailing comment. Probed the same day:
An updater that matched only the code part of a line would stop reading every pinned action and every frozen
rev:in the repository.The rule that holds is narrower: a reference may not start inside a comment. What follows the reference stays
readable, so the trailing version comments keep working.
Scope
leads are the ones
domain/marker.pyalready spells, so they are named once. Quoting matters: arepo:valueholds
https://, which is not a comment._reference_match, the pre-commit pass, and the jsDelivr pass. The last two walk their own lines.# update-time:marker is a comment, andparse_markergoes on reading thewhole line.
Out of scope
Four defects the same audit found, each with a cause of its own:
YAML_IMAGE_REFERENCEmatches any key ending inimage, soinit_image: busybox:1.36is taken for an imagereference (
sources/oci.py:69)._ATTRIBUTES_REgives a resolved integrity hash to the next line holding a{, so a bare-string jsDelivr URLwrites
"integrity"into whatever dictionary follows it (updaters/update_jsdelivr.py:44)._IMAGE_REmatches a line-initialFROMinside a BuildKit heredoc body, so embedded SQL is looked up as animage (
updaters/update_dockerfile_base_image.py:21)._STAGE_NAME_REis greedy and case-insensitive, so a trailing comment's "as" is captured as the stage name andthe real stage goes unregistered (
updaters/update_dockerfile_base_image.py:27).A
uses:inside arun:shell string is out of scope too. It sits in a string rather than a comment, so the ruleabove does not reach it.
Documentation
user-visible.
thing a reader would assume.
Open questions
Line, which already carries the text a marker is read from, or inprimitives/text.pybesideline_numberandcolumn?scanned files hold; a Python triple-quoted string spans lines, which a line-based pass cannot see.
# image: redis:6.2 # update-time: ignore? The reference is not updated either way, so the question is onlywhether such a marker is reported as recognised.