fix(claude-ops): resolve the Explore and Plan disallowed-tools spread in its own scope - #5717
Conversation
… in its own scope A `...spread` in an agent's tools or disallowedTools now reads the binding its own module and scope see (`_binding_value`, the rule name and field resolution use), not the nearest same-name binding in the bundle. On 2.1.286 that nearest binding was an unrelated call, so Explore and Plan lost the shared entries, Artifact tools included, and read `partial`. A spread whose scoped binding is not an array literal stays `partial`. Closes #5711 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ns its binding
`var pY=[a];function init(){pY=[b]}` makes a later `...pY` read b once
init has run, but the scoped lookup returned the declaration's a. The
spread path now treats a binding that another function in the module
assigns (and does not shadow) as not static, so the list stays
`partial`. The check is confined to the spread path: applied inside
`_binding_value` it changed many unrelated description fields on both
2.1.285 and 2.1.286.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`_written_elsewhere` only counted writes in another function, so
`var pY=[a];if(c){pY=[b]}` (or a loop body) and an expression-bodied
arrow `var f=()=>pY=[b]` still read `literal` a. A write now counts when
it sits in a different block than the declaration or inside an arrow
expression, including when the arrow's own assignment is the binding
`_binding_value` picked. The shadow check also read declarations at
doubled offsets; it now uses the absolute positions finditer returns.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude-ops 0.79.3 landed on main (#5706); this change moves to 0.79.4. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Claude finished @kyle-sexton's task in 50s —— View job Security review: no issues foundRan
Scope reviewed: all 5 changed files — Assessment: No security-relevant surface. This PR fixes a scope-resolution bug in a static-analysis script (
GitHub Actions hardening (triggers, permissions, pinning) is out of scope for this lane (zizmor's) and not applicable here regardless, since no workflow files changed. No findings to report. |
|
Claude finished @kyle-sexton's task in 4m 6s —— View job Code review complete
SummaryTraced One finding, posted inline:
No other correctness issues found in the changed files. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d723da7ff4
ℹ️ 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".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…onal write
`var pY=[a];if(c)pY=[b]` (or `pY=c?[b]:pY`, `c&&(pY=[b])`, an unbraced
`for(...)pY=[b]`) made `_binding_value` select the bare assignment, and
`_written_elsewhere` skipped same-block writes, so the spread read [b]
as a literal. A spread's binding must now be a declaration, and any
other write to it in the module, including one in the same block, makes
the list `partial`. A `var` reached back through a chain of simple
declarators counts as a declaration: on 2.1.285 and 2.1.286 the shared
`pY` follows a function declaration with no `;`, which `_declares`
misses.
extraction.md no longer labels `function init(){...}` as an
expression-bodied arrow.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 1m 27s —— View job Security review: no issues found
Scope reviewed: diff from Assessment: No security-relevant surface, same conclusion as the prior review of this PR. This is a static-analysis script (
No findings to report. |
|
Claude finished @kyle-sexton's task in 4m 51s —— View job Code review complete
SummaryRan I traced
I also checked the No new findings. The one open item (arrow over-match) is a known, already-discussed, fail-closed |
|
@codex review |
|
Codex Review: Something went wrong. Try again later by commenting “@codex review”. ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 24d26c52a9
ℹ️ 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".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…o a spread's binding
`var pY=["A"];if(c){var pY=f()}` redeclares the same function-scoped
binding, but `_written_elsewhere` skipped every declarator and took it as
a shadow, so the spread read A as a literal. A declaration now shadows
only when it introduces a separate binding: a `var` in another function,
or a `let`/`const` in another block. A `var` in the same function is the
binding again and its initializer counts as a write.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 1m 0s —— View job Security review: no issues found
Scope reviewed: diff from Assessment: No security-relevant surface, consistent with the prior reviews posted on this PR's earlier commits (
No findings to report. |
|
Claude finished @kyle-sexton's task in 7m 52s —— View job Code review complete
SummaryThis is the final commit (
No new findings to report. The one previously-flagged, already-disposed item (arrow-detection over-matching the whole statement prefix) remains an accepted, fail-closed limitation tracked under #5640, not a defect introduced here. Not independently verified: I don't have code-execution permission in this environment ( |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 53372f0a6e
ℹ️ 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".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…e Code 2.1.287 (#5731) Closes #5730 ## Summary Per-release native-surface pass for Claude Code 2.1.287. Every inventory lane extracts ok on 2.1.287, so the inventory is revalidated against it. The one native surface that moved is the hidden built-in command `/plugin-types`, which the 2.1.287 build no longer ships. Its dismissal against `code-metrics:audit-type-debt` was orphaned and is removed. ## Fix - `VALIDATED_AGAINST` in `plugins/claude-ops/skills/inventory/scripts/inventory.py` is now `2.1.287`. - The `plugin-types` / `code-metrics:audit-type-debt` dismissal is removed from `docs/native-surfaces/records.json`. `overlap.py` has no undismiss command, so the record was deleted from the store and `docs/native-surfaces.md` was regenerated with `overlap.py generate`. `node scripts/generate-catalog.mjs` reported the catalog already in sync. - claude-ops is bumped from 0.80.0 to 0.80.1, with a CHANGELOG entry. ## Verification - Binary string search, not variable names: in 2.1.286, `/plugin-types` occurs 10 times and `Write claude-code.d.ts` 2 times. In 2.1.287 both occur 0 times. The only `plugin-types` left in 2.1.287 is the CSP directive name inside a list of `*-src` directives. - Surface diff, 2.1.286 final extraction against 2.1.287: builtin_commands went from 111 to 110 (`plugin-types` removed). Nothing was added or renamed in any lane. The Explore and Plan `disallowed_tools` now read `literal` with the Artifact tools included. That comes from the extractor fix in #5717, not from a change in the binary. - `inventory.py --self-check`: `OK: cli 2.1.287, validated against 2.1.287`, all six lanes ok, exit 0. It was `DEGRADED` (exit 3) before the bump. - `overlap.py detect` on the final extraction: exit 0, integrity ok, discovered 0, resurfaced 0, orphaned dismissals 0, 95 suppressed. - `overlap.py self-check`: exit 3, degraded only by the 2 standing advisories it also reports on main (older recorded extraction versions on rows, upstream SHA not locally decidable). 69 rows checked, 0 problems. - `test_inventory.py`: 215 tests OK. `test_overlap.py`: 184 tests OK. Pinned ruff passes on `inventory.py`. ## Related - #5704: the same pass for 2.1.286. - #5717: the Explore and Plan disallowed-tools extractor fix. - #5730: the native-drift item `native-drift:inventory-degraded:2.1.287:inventory`, filed by this pass and closed by this PR. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Closes #5711
Summary
On Claude Code 2.1.286 the inventory read the built-in Explore and Plan agents'
disallowedToolsaspartial. Both definitions spread a shared list (...pY). The spread resolver took the nearestpY=binding anywhere in the bundle, which on 2.1.286 is an unrelatedpY=p(...)call, so the shared entries, the Artifact tools among them, were dropped.Fix
_array_namesresolves a...spreadelement through a new_spread_arrayhelper. It reads the binding with_binding_valueat the spread's own offset, the same module and scope rule_scoped_constantapplies to names and fields (fix(claude-ops): resolve built-in descriptions the inventory left unresolved #5619, fix(claude-ops): leave a built-in name unresolved when it is bound to a non-constant expression #5704). Before, it used_nearest_binding.partial.if(c)pY=["B"],pY=c?["B"]:pY,c&&(pY=["B"]), an unbracedfor(...)pY=["B"]), or any other write in the same block reaches it, the list stayspartial. Avarreached back through a chain of simple declarators counts as a declaration, which keeps Explore and Plan resolving: theirpYfollows a function declaration that has no;, and_declaresmisses that statement.partial. That code is a nested block (if(c){pY=["B"]}, a loop body), another function (function init(){pY=["B"]}), or an expression-bodied arrow (var f=()=>pY=["B"], in either order relative to the declaration). A read can then see B, so the list is not static. This check (_written_elsewhere) applies only to the spread path. Applied inside_binding_value, it changed many unrelated description fields on both 2.1.285 and 2.1.286 and made the run about 5x slower, so it is not shared with name and field resolution.test_inventory.py:partial.ifor loop block, or an expression-bodied arrow reassigns stayspartial.reference/extraction.mdstates the spread rule. claude-ops 0.79.2 -> 0.79.3, with a CHANGELOG entry.Verification
inventory.py --binary-onlyon 2.1.286, diffed against the prior run (.work/cc-2.1.286/inv-final.json). Only these fields changed:builtin_agents/Explore/disallowed_tools:[Agent, ExitPlanMode, Edit, Write, NotebookEdit]->[Agent, Artifact, ArtifactComments, ArtifactData, ArtifactCheck, ExitPlanMode, Edit, Write, NotebookEdit]builtin_agents/Explore/disallowed_tools_source:partial->literalbuiltin_agents/Plan/disallowed_toolsanddisallowed_tools_source: the same changeinventory.py --self-check:OK: cli 2.1.286, validated against 2.1.286, with all six lanes ok.python3 -m unittest test_inventory:Ran 215 tests ... OK.overlap.py detect: exit 0, withdiscovered: 0andresurfaced: 0.overlap.py generate --checkreports the docs are in sync.node scripts/generate-catalog.mjsproduced no diff.scripts/run-ruff.sh checkandformat --checkpass on both files.Related
🤖 Generated with Claude Code