Skip to content

What Muse's read of #634 left: the mutant lists read in order, a comparison's own operator, the head commit's trailer, the merge base (#589) - #664

Open
don-petry wants to merge 8 commits into
mainfrom
untested-634-reads
Open

don-petry wants to merge 8 commits into
mainfrom
untested-634-reads

Conversation

@don-petry

@don-petry don-petry commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Part of #589. This is the follow-on to #634, the advisory check of a changed line of Python no test notices. #634 merged before Muse could read it: Muse was out of quota until 2026-11-09. Muse Wonder Full read it after the merge, at 60316bb, and found four majors. All four are real, and each is fixed here.

Decision 0166 gains Boundaries rows only; its words are not changed. No protected file is touched, and no record is added.

Muse's four findings on #634, fixed

Finding The fix Held by
Any call in the mutant lists was data, so an entry that read the world, such as os.getenv('X', 'old'), escaped the check An entry is data only if every name it reads was bound before the list to a text, a literal value or a helper the module defines; see below test_a_call_in_the_mutant_lists_is_data_only_of_the_modules_own_helpers, 27 mutants
A comparison's fallback mutant took the first operator the line showed, even one inside a string, so a test that read the text could kill a mutant that changed nothing The operator is found between the comparison's two sides. A comparison it cannot swap, or one on another line of the statement, is passed over test_a_mutant_is_made_of_an_added_line_and_never_of_a_quiet_one, 5 mutants (<, >, >=, the guards)
Nothing held that the trailer is read from the head commit alone A trailer in an earlier commit of the change is never read, and the commit asked for is the one read, not the checkout's HEAD test_a_trailer_is_read_from_the_head_commit_alone, 2 mutants
Nothing held that the change is read from the merge base Main moving on after the branch left it adds no hunk to the change test_the_change_is_read_from_the_merge_base_and_not_from_main_as_it_moved_on, 1 mutant

What is data in the mutant lists, now

The check skips a hunk only when it is data: an entry of ISSUE or MUTANTS that decides nothing. The fix rounds found many routes by which an entry that reads the world passed for data. The rule is now stated in terms of what is allowed, not what is forbidden:

  • A name is data only if a statement before the list bound it to one of these:
    • a text;
    • a value that is itself data;
    • a function the module defines, with no decorator.
  • A name bound any other way is code:
    • bound twice;
    • bound by an import, wherever the import stands;
    • bound by a walrus;
    • named by a global;
    • changed by a method call, a store into it or a del;
    • left unbound.
  • A call of globals, locals, vars, exec, eval, setattr or delattr makes every name code.
  • Calls:
    • a call is data only of such a helper, or of replace on a text;
    • a helper's return counts as a text only if the helper says -> str;
    • a dunder attribute is code.
  • The real lists read exactly as before: data_lines of tools/mutants.py is the same under main's untested.py and this one.

Residual, stated, not fixed: a contrived evasion is left out. One example is a nested scope whose own name masks the module's. The check is advisory, and the record already says the head's own tests can forge its counts. The hunk that reads the world sits outside the lists and is still checked.

A deliberate narrowing: a text method other than replace, such as .strip() or .format(...), is now code where main took it for data. No entry of the real lists changes.

Checks

  • make verify on an export of the head: 1716 tests OK, at 2b47efa.
  • Every mutant of tools/untested.py, run by tools.mutants on an export of the head: 83 of 83 killed at 2b47efa. 35 of them are new.
  • All mutant targets occur once in their files, and no description repeats.

Reads

Argon, Muse Wonder Full, the Claude review and the security pass read the first head, eb184e4. The Claude review read each fix round again: fe00bf1, c0065ce and 2424df1. Argon read 2424df1. The last two rounds, 5626728/dd25df3 and 2b47efa, close what those reads found, and add a case for a mutant that survived; no reader read them again.

Reviewer Head Unread parts Blocking Major Minor Info
agy eb184e4 none 0 2 0 0
muse eb184e4 none 0 2 3 0
claude-review eb184e4 none 0 0 3 0
claude-security eb184e4 none 0 0 1 0
claude-review-r2 fe00bf1 none 0 2 2 0
claude-review-r3 c0065ce none 0 1 3 0
agy-r4 2424df1 none 0 8 0 0
claude-review-r4 2424df1 none 0 0 1 4
Finding Severity Criterion Where What Disposition
agy#1 major PR-3 tools/untested.py:200 The receiver guard _literal(func.value, helpers) on .replace calls has no negative test and no mutant Fixed in fae6b69: replace is data only on a text; ROOT.replace is code.
agy#2 major PR-3 tools/untested.py:200 _literal treats os.replace(...) and any module's replace function as literal data Fixed in fae6b69: os is a name an import binds, so os.replace is code.
muse#1 major PR-3 tools/untested.py:162 Helper detection ignores binding order, so a shadowed name calling os.getenv is data Fixed in fe00bf1: the module is read in order; a name bound twice is code, and a list sees only what was bound before it.
muse#2 major PR-3 tools/untested.py:200 Any object's replace, including os.replace, is treated as mutant-list data Fixed in fae6b69, as agy#2.
muse#3 minor PR-3 tools/untested.py:200 No test holds the replace receiver check against a deciding receiver Fixed in fae6b69 and fe00bf1: open('x').read().replace and os.replace are held as code.
muse#4 minor PR-3 tools/untested.py:484 No test holds the > and >= entries of the comparison SWAP table Fixed in fe00bf1: > and >= are swapped in the test, with a mutant each.
muse#5 minor PR-3 tools/untested.py:521 Trailer test never calls trailers() for a commit other than the current HEAD Fixed in fe00bf1: the earlier commit is asked for after the fix round too, with a mutant.
claude-review#1 minor PR-3 tools/untested.py:191 An entry of the mutant lists that reads the world through a subscript or attribute is still data Fixed in fae6b69: a comparison it cannot swap is passed over, with a case and a mutant.
claude-review#2 minor PR-3 tools/untested.py:498 No test holds the one-line guard on the Compare: dropping inner.lineno == inner.end_lineno == node.lineno survives Fixed in fae6b69: a comparison on another line is passed over, with a case and a mutant.
claude-review#3 minor PR-3 tools/untested.py:499 No test holds the SWAP guard on the comparison's operator: a mutant that removes it survives, and the change has no mutant for it Fixed in fae6b69 and c0065ce: what is read from a name an import binds is code.
claude-security#1 minor PR-3 tools/untested.py:200 _literal still treats some world-reading entries in the mutant lists as data: any receiver's .replace, and subscripts or attributes of modules Fixed in fae6b69, as claude-review#3 and agy#1.
claude-review-r2#1 major PR-3 tools/untested.py:192 An import nested in if/try/with, or a star import, binds nothing in _bind, so what it gives is data in the mutant lists Fixed in c0065ce: a name is data only where a statement before the list bound it; guarded and star imports and ENV = os.environ are code.
claude-review-r2#2 major PR-3 tools/untested.py:210 No test holds the binding _bind reads from statements other than def/import/assign (for, with, AugAssign, class) Fixed in c0065ce: a for loop's rebinding is held, with a case and a mutant.
claude-review-r2#3 minor PR-3 tools/untested.py:193 No test holds that a dotted import binds its first name as code Fixed in c0065ce: a dotted import binds its first name, with a case and a mutant.
claude-review-r2#4 minor PR-3 tools/untested.py:207 No test holds that an unpacking other than A, B = x, y binds its names as code Fixed in c0065ce: another unpacking binds its names as code, with a case and a mutant.
claude-review-r3#1 major PR-3 tools/untested.py:193 A name rebound or filled by something other than a plain assignment target keeps its first kind, so an entry that reads the environment is still data Fixed in 2424df1: a walrus, a global, a method and globals() or eval unsettle a name, each with a case and a mutant.
claude-review-r3#2 minor PR-3 tools/untested.py:193 No test notices that an annotated assignment is read as an assignment Fixed in 2424df1: a dunder attribute and a decorated def are code.
claude-review-r3#3 minor PR-3 tools/untested.py:231 An attribute chain off a helper or a text reaches os without a call, and is data Fixed in 2424df1: replace on a helper's return is data only where the helper says -> str.
claude-review-r3#4 minor PR-3 tools/untested.py:253 _text takes any helper's return for a text, so replace on a helper's Path is data while ROOT.replace is code Fixed in 2424df1: an annotated text is held, with a mutant.
agy-r4#1 major PR-3 tests/test_untested.py:354 Import and rebinding cases in test_a_call_in_the_mutant_lists_is_data_only_of_the_modules_own_helpers do not test what they claim Declined: the bare import cases are vacuous now that an unbound name is code, but their bound-first siblings (os = 'a' then an import) kill each import mutant.
agy-r4#2 major PR-3 tools/mutants.py:15389 Calls to helpers after globals() or eval() bypass bound["*"] == "code" Fixed in 5626728: a helper's call is code after globals() or eval.
agy-r4#3 major PR-3 tools/mutants.py:15406 Mutant "a helper defined after the mutant lists is theirs" disables binding before the lists instead Fixed in dd25df3: the mutant is renamed "no statement before the mutant lists binds a name", for what it does.
agy-r4#4 major PR-3 tools/untested.py:167 Listed chained assignment in data_lines skips _bind and leaves prior bound names settled Declined: data_lines binds a listed statement's other names after it reads the list (the _bind after the loop), so x in MUTANTS = x = [...] is code for a later list.
agy-r4#5 major PR-3 tools/untested.py:201 _bind pairs tuple unpacking 1-to-1 even when the RHS tuple contains starred items Fixed in dd25df3: an unpacking with a starred item is no pairing, with a case and a mutant.
agy-r4#6 major PR-3 tools/untested.py:214 _bind and _unsettle ignore del statements and in-place subscript mutations in functions Fixed in dd25df3: del, and a store through a subscript or attribute, make a name code, each with a case and a mutant.
agy-r4#7 major PR-3 tools/untested.py:247 _unsettle misses method mutations when a function precedes the variable or shares a nested name Fixed in dd25df3 for a helper defined before its name is bound. Left: a nested scope's own name masking the module's, which only a contrived helper meets.
agy-r4#8 major PR-3 tools/untested.py:273 _literal allows calling a helper after globals() or exec() unsettles all names Fixed in 5626728, as agy-r4#2.
claude-review-r4#1 minor PR-3 tools/untested.py:286 _text's check that a call is itself literal is held by no test: a mutant that drops it survives Fixed in 5626728: the literal check on a call read as a text is held, with a case and a mutant.
claude-review-r4#2 info none tools/untested.py:224 Most of REFLECTS, and the Store-local half of own, are held by no test Fixed in 5626728, as agy-r4#2.
claude-review-r4#3 info none tools/untested.py:247 A helper that changes a module list before the list is bound, or through a subscript, leaves the name data Fixed in dd25df3 (a helper filling a list early, a subscript store), as agy-r4#6 and #7.
claude-review-r4#4 info none tools/untested.py:273 After a call of globals() or eval, a helper's call is still data, though decision 0166 says such a name is code Left: exec, vars, locals, setattr and delattr share globals' path, which globals() and eval hold; the Store half of own only makes more entries code.
claude-review-r4#5 info none tools/untested.py:406 Text methods other than replace, and replace on a helper's tuple item, are now code where main took them for data Deliberate: only replace on a text is data; no entry of the real lists changes.

🤖 Generated with Claude Code

View guided diff Turn on auto-fix

don-petry and others added 8 commits November 8, 2026 19:05
…e head commit's trailer and the merge base (#589)

Muse Wonder Full read #634 after it merged (it was out of quota until 2026-11-09); its
four majors are real, and each is fixed here with a test, a mutant and a Boundaries row
in decision 0166:
- A call in the mutant lists was data whatever it called, so an entry that read the
  world, as os.getenv('X', 'old'), escaped the check. Now a call is data only of a helper
  tools/mutants.py defines, or of a text's replace; a builtin's or another module's is
  code, as the record says.
- A comparison's fallback mutant took the first operator the line showed, one in a string
  among them, and a test that read the text killed a mutant of nothing. Now the operator
  is found between the comparison's two sides.
- Reading the head commit's trailer alone, and diffing from the merge base, were held by
  nothing: each fixture had one commit on its base. Two tests now give a trailer in an
  earlier commit, and main moving on after the branch left it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ode in the mutant lists; the comparison's guards are held (#589)

The Claude review and the security pass of eb184e4:
- An entry that read the world through an attribute or a subscript, as os.environ['X'] or
  sys.platform, or by replace on a path, was still data. Now _bound reads what each name
  the module binds is: a name an import binds is code, wherever an entry reads it, and
  replace is data only on a text (a constant, a name given one, or what an allowed call
  gives). Two mutants: "a replace on anything is data in the mutant lists" and "what is
  read from a module in the mutant lists is data".
- The comparison's two guards were held by nothing: a comparison it cannot swap, as ==,
  and one on another line of the statement. The candidates test now feeds both, with a
  mutant for each.
The two Boundaries rows this change added say so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…em, and a name bound twice is code (#589)

Argon's and Muse's reads of eb184e4. Their findings on replace were fixed in fae6b69; three
more were real:
- Helpers were every def in the module, so a name an import bound before the list and a
  def rebound after it, as getenv, was a helper. Now _bind reads the module's top in order:
  a list sees only what was bound before it, a name bound twice is code, and A, B = x, y
  binds each name to its own value, as tools/mutants.py does. The real lists read exactly
  as before: 15260 lines of data either way.
- The SWAP table's > and >= were held by nothing; the candidates test swaps both.
- The trailer test always asked for the checkout's own HEAD; it now asks for the earlier
  commit after the fix round too.
Six mutants, and the three Boundaries rows this change added name them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… them to a text, a value or a helper (#589)

The Claude review of fe00bf1: an import behind an if, a try or a with, a star import, and
a name bound to what os gave, as ENV = os.environ, were all data, because a name nothing
was known to bind was taken for data. Now that is turned round: a name is data only where
a statement before the list bound it to a text, to a value _literal takes, or to a helper
it defines; an import binds its first name wherever it stands among the statements, and
anything else any statement binds, or binds again, is code. The real lists read exactly as
before. Five mutants, each killed by the cases the review gave, and by cases that bind
the name as a text first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…a decorator and a dunder unsettle a name (#589)

The Claude review of c0065ce: a name kept its first kind when something other than an
assignment changed it. Now _unsettle makes code each name a walrus binds anywhere, a
global or nonlocal names, or a method is called on (but a text, which none changes, and a
function's own name); a call of globals, locals, vars, exec, eval, setattr or delattr makes
every name code. A decorated def is code, a dunder attribute is code, and replace is data
on a helper's return only where the helper says it gives a str. The real lists read
exactly as before. Ten mutants, each killed by the review's cases or by the case that
keeps a helper's own name, an annotated text and a text's method as they were.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e after globals() (#589)

The Claude review of 2424df1: _text's own check that the call is data was held by no
test, so os.environ['X'].replace(...).replace(...) could pass; and a helper's call
stayed data after a call of globals or eval, which may rebind it. Both are now held, each
with a case and a mutant.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… a helper that fills a list early are code (#589)

Argon's read of 2424df1. Its findings on a helper's call after globals() were fixed in
5626728, and the one on a chained assignment misread data_lines, which binds a listed
statement's other names after it reads the list. Four more were real, and each is held now:
a store through a subscript or an attribute, and a del, change what a name is; an
unpacking with a starred item is no pairing; and a method a helper calls on a name not
bound yet makes it code once bound, unless the method is one a text has, so a helper that
reads a text defined after it leaves the text a text. The mutant once called "a helper
defined after the mutant lists is theirs" is named for what it does. The real lists read
exactly as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d a case holds it (#589)

The mutant "a call of any method in the mutant lists is data" survived the run of
every untested.py mutant at dd25df3: no case called a method other than replace on a
text. TEXT.format(1) now does, and is code, as the narrowing says.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@don-petry
don-petry requested a review from a team as a code owner November 9, 2026 03:22
@coderabbitai

coderabbitai Bot commented Nov 9, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

Run configuration

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@chatgpt-codex-connector

Copy link
Copy Markdown
Codex Review Summary: ❌ Failed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ❌ Failed 2026-11-09T03:22:57.179576Z
We couldn’t complete this review. Comment @codex review to try again.
2b47efa PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@sonarqubecloud

sonarqubecloud Bot commented Nov 9, 2026

Copy link
Copy Markdown

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant