security(codeql): suppress py/unsafe-cyclic-import on simulation triple (closes #215)#216
Conversation
…le (closes strands-labs#215) The `py/unsafe-cyclic-import` rule walks `if TYPE_CHECKING:` blocks for cycle detection, which makes it fire on the deliberate static-only cycle in strands_robots/simulation/{base,policy_runner,benchmark}.py. PR strands-labs#209 exhausted its round budget attempting to satisfy the rule via code surgery and confirmed the constraint conflict between mypy's name-resolution requirement and CodeQL's AST-walk is not solvable in this file shape. This change is the Option A path documented in PR strands-labs#209 S13/R6: a documented false-positive suppression delivered as repo-level CodeQL config rather than further code churn. Why the runtime is safe: - Every affected file uses `from __future__ import annotations` (PEP 563), so all type hints are string-form at runtime and are never resolved at module-import time. - The cycle is structurally required: policy_runner needs to call into the SimEngine ABC defined in base, while base must advertise PolicyRunner- typed return values to static checkers. Both conditions can hold only if the cycle is closed under TYPE_CHECKING (= AST-only, runtime-free). - Pin tests guard the runtime invariant independently of CodeQL: - tests/simulation/test_no_cyclic_imports.py spawns a fresh interpreter for each affected module and asserts each imports cleanly with no recursion error. - tests/simulation/test_no_import_cycle.py::test_no_runtime_import_cycles builds the runtime import graph (excluding TYPE_CHECKING edges) via networkx.simple_cycles and asserts it is acyclic. Why a global suppression rather than path-scoped: CodeQL's `query-filters` keys filter by `id` / `tags` / `precision` only; path-scoped exclusion of a single query is not supported (the only path-aware key is `paths-ignore`, which excludes a file from all queries - too broad). The simulation triple is the only place this query fires in the repo today, so a global exclude is equivalent in effect and keeps the config small. A future contributor introducing a new legitimate violation in unrelated code should drop this suppression and fix the new cycle properly; the regression tests above will fail loudly if anyone hoists a lazy import to module scope and re-creates the runtime cycle. Files changed: - .github/codeql/config.yml -- new; query-filters exclude - .github/codeql/README.md -- new; full per-rule rationale, regression contract, and extension guidance - .github/workflows/codeql.yml -- wire config-file into init step - .github/workflows/codeql-advanced.yml -- wire config-file into init step - strands_robots/simulation/base.py -- one-block breadcrumb pointing at .github/codeql/{config.yml,README.md} above the existing CodeQL-discussion comment - strands_robots/simulation/policy_runner.py -- breadcrumb above the TYPE_CHECKING block that closes the cycle - strands_robots/simulation/benchmark.py -- breadcrumb above the TYPE_CHECKING block that closes the cycle This commit closes the recurrent CodeQL alerts strands-labs#83-strands-labs#87, strands-labs#253-strands-labs#255 once the workflow runs against the new config; PR strands-labs#209 can then close as superseded.
yinsong1986
left a comment
There was a problem hiding this comment.
Summary
PR globally suppresses the CodeQL py/unsafe-cyclic-import rule via a new .github/codeql/config.yml, wires it into both CodeQL workflows, and adds 4-7 line breadcrumb comments above the TYPE_CHECKING blocks in simulation/{base,policy_runner,benchmark}.py. Net change is config + comments only; no runtime behavior touched. The technical rationale (PEP 563 + the fact that policy_runner only re-imports base under TYPE_CHECKING, so no actual runtime cycle exists) is sound, and the scope discipline is good (suppression-only, no surgery in #209).
The substantive concerns are all in the supporting documentation, which includes several factual errors that, if left, will actively mislead the next person debugging this:
- The README's runtime-safety argument cites a
_lazy_policy_runner()shim that does not exist anywhere in the codebase (verified with grep acrossstrands_robots/andtests/). The real reason runtime is safe is the asymmetric import (base imports policy_runner at module level; policy_runner only imports base under TYPE_CHECKING). Inventing a shim makes the safety argument fail audit. - One of the two pin tests claimed by the regression contract does not exist. Both
config.ymlandREADME.mdcitetests/simulation/test_no_cyclic_imports.py; onlytests/simulation/test_no_import_cycle.pyis present in the tree. So the "two pins fail loudly" claim is half-fictional. - The inline comment in
config.yml(lines 29-32) contradicts itself about what the global suppression does: it says the filter "only suppresses the alert surfacing, not the analysis itself" while simultaneously claiming alerts on unrelated files "will still appear in the Security tab". They won't — that's exactly whatquery-filters: excluderemoves from the SARIF results. The README's own "Why a global suppression" section gets this right, so the two docs are now mutually inconsistent.
None of these are blockers for the suppression mechanism itself — the YAML schema is valid, the config wiring is correct, and the per-file breadcrumbs are useful. But the docs need a pass before the README becomes authoritative.
What's good
- Both CodeQL workflows wired (
codeql.yml+codeql-advanced.yml) so suppression applies to push/PR and the matrix run. - Per-file breadcrumb comments point readers at
.github/codeql/README.mdrather than dead-ending in the source. Good discoverability practice. - AGENTS.md non-ASCII rule is clean on every touched line (verified).
- Scope is tight: config + comments, no
simulation/code changes, supersedes #209 cleanly.
Concerns
- Doc accuracy (see inline comments 1-3): invented shim, missing pin test, self-contradicting comment. All fixable in one follow-up commit.
- Pre-existing action pinning gap in
codeql-advanced.yml: lines 60, 70, 102 still use@v4floating tags rather than 40-char SHAs. Per AGENTS.md > Review Learnings (#92) > Action Pinning, everyuses:should pin to a SHA with the version tag as a trailing comment. This PR did not introduce this (came in via #189), but it touched the file three lines belowinit@v4and it's the natural moment to fix it. Out of scope for this PR; worth a follow-up issue on the project board. - Dependabot
github-actionsecosystem entry: confirm.github/dependabot.ymllistsgithub-actionsso the newcodeql/config.ymlreferences stay current. (Not blocking; just the kind of thing that goes stale silently.)
Verification suggestions
# 1. Confirm the YAML actually parses to a CodeQL-recognisable shape.
python3 -c 'import yaml; cfg=yaml.safe_load(open(".github/codeql/config.yml")); assert any("py/unsafe-cyclic-import" in str(f) for f in cfg.get("query-filters", [])), cfg'
# 2. Confirm the asymmetric-import claim (the actual runtime-safety mechanism).
python3 -c '
import ast, pathlib
for p in ["strands_robots/simulation/base.py", "strands_robots/simulation/policy_runner.py"]:
tree = ast.parse(pathlib.Path(p).read_text())
print(p, "module-level imports of the other:")
for n in tree.body:
if isinstance(n, ast.ImportFrom) and n.module and "simulation" in n.module:
print(" ", n.module)
'
# 3. Re-run the existing pin and confirm it passes on this branch.
hatch run pytest tests/simulation/test_no_import_cycle.py -v
# 4. After merge, inspect Security tab on main and confirm alerts #83-#87, #253-#255 auto-close on the next CodeQL run.…terpreter pin Three doc-accuracy fixes raised in R1 review feedback, addressed in one commit (single concern: faithful documentation of the suppression rationale). 1. README.md cited a non-existent `_lazy_policy_runner()` shim as the runtime-safety mechanism. The real mechanism is the asymmetric edge: base.py:50 imports PolicyRunner / VideoConfig at module level (the runtime edge), but policy_runner.py:51-54 imports SimEngine / BenchmarkProtocol / Policy from base only under TYPE_CHECKING -- there is no runtime back-edge, so the cycle never closes at import time. PEP 563 is additional belt-and-braces, not the load-bearing argument. README rewritten to spell out the asymmetric-edge mechanism with file and line references. 2. README.md and config.yml both referenced `tests/simulation/ test_no_cyclic_imports.py` as a regression pin. That test did not exist; only `test_no_import_cycle.py` was present. Per the review thread's recommendation (add the test rather than remove the reference), the new file ships in this commit -- a fresh-interpreter subprocess import smoke-check that is genuinely complementary to the existing AST-graph cycle scan. It catches dynamic-import failure modes (partial-module ImportError on combined import order) that the static graph would miss. 3. config.yml inline comment was self-contradicting: it claimed both that future unrelated alerts would 'still appear in the Security tab' and that the filter 'only suppresses the alert surfacing'. Both cannot be true -- query-filters: exclude removes matching alerts from SARIF entirely. The README's 'Why a global suppression' section already had the honest framing; config.yml is now aligned with it (cost: future legitimate violations in unrelated code are silently suppressed too; mitigation: pin tests guard runtime independent of CodeQL, and a future contributor should drop this filter rather than extend it). Bonus: base.py breadcrumb (raised on review thread strands-labs#4) tightened to match. Previously leaned on PEP 563 as the safety argument; now spells out the asymmetric edge with an explicit do-NOT-hoist instruction ('Do NOT hoist the TYPE_CHECKING import in policy_runner.py to module scope') and the exact ImportError shape that hoisting produces. PEP 563 is correctly demoted to belt-and-braces. Pre-fix verification of the new pin (AGENTS.md S0 mandatory pin-on-pre-fix round-trip): $ # Hoist policy_runner.py's SimEngine import out of TYPE_CHECKING $ # (the precise regression shape the pin exists to catch) $ python -m pytest tests/simulation/test_no_cyclic_imports.py -v 4 failed -- ImportError: cannot import name 'SimEngine' from partially initialized module ... $ # Restore $ python -m pytest tests/simulation/test_no_cyclic_imports.py -v 4 passed Post-fix full simulation suite: 235 passed, 43 skipped (optional deps absent on local). No production code touched -- doc + breadcrumb + new pin only. Out of scope (deferred per AGENTS.md scope discipline): - Action SHA-pinning of codeql-advanced.yml (review thread strands-labs#5, explicitly marked as 'out-of-scope nit ... worth a follow-up issue on the project board' in the review). Will land as a separate follow-up issue, not this PR. Closes none; addresses R1 of strands-labs#216.
… CI lint failure)
yinsong1986
left a comment
There was a problem hiding this comment.
Summary
Config-only PR that suppresses CodeQL's py/unsafe-cyclic-import globally via a new .github/codeql/config.yml, wires it into both CodeQL workflows, adds per-file breadcrumbs in the simulation triple, and pins the runtime invariant with a fresh-interpreter regression test. Scope matches the description: 8 files, no behavior change. The runtime-safety argument (asymmetric cycle: base -> policy_runner at module level; back-edge only under TYPE_CHECKING) is sound, and the README rationale + per-file pointers make this auditable in a year. Local tests pass on this checkout.
What's good
- Both workflows wired, not just one. Easy thing to forget.
- Per-file breadcrumb so a future reader hitting
simulation/base.pyfinds the rationale without grep-spelunking. - Two complementary pin tests (static AST graph + dynamic fresh-interpreter import) so a regression caught either way is caught at PR time, not at the next CodeQL run on
main. - AGENTS.md non-ASCII rule clean on every line touched by this PR (verified).
- README explicitly enumerates the trade-off of a global vs path-scoped filter rather than papering over it.
Concerns
- End-to-end verification is out-of-band. The config takes effect on the next CodeQL run against
mainafter merge; there is no way to confirm the suppression actually fires on this PR until then. Consider asking a maintainer to confirm the alerts auto-close via the Security tab post-merge, and rolling back if they do not. - Action SHA pinning regression on the file this PR touches.
.github/workflows/codeql-advanced.ymlis being modified here, but lines 60/70/102 still use floating tags (actions/checkout@v4,github/codeql-action/init@v4,github/codeql-action/analyze@v4). AGENTS.md "Review Learnings (PR #92) > Action Pinning" calls these out as non-negotiable, and the PR description defers them to follow-up #217. Since this PR is the only one touching that file with security-relevant changes, fixing here would be cheap; the deferral is reasonable but worth flagging since it leaves the workflow in a known-broken state w.r.t. the supply-chain rule. queries:vsconfig-file:interaction. Per the CodeQL docs and the comment that already exists atcodeql-advanced.yml:78-79, a workflow-levelqueries:value overrides config-file queries unless prefixed with+.query-filtersshould still apply on top, but the interaction is subtle enough that a one-line note in the README confirming "queries: security-and-qualityincodeql.ymlcontinues to be the active suite; the config layersquery-filterson top" would save the next reader a trip to the docs.
Verification suggestions
# Sanity-check the new pin test on a checkout of this branch:
hatch run test tests/simulation/test_no_cyclic_imports.py tests/simulation/test_no_import_cycle.py -v
# After merge, the human reviewer should:
# 1. Watch the next CodeQL run on `main` complete (Security tab).
# 2. Confirm alerts #83-#87, #253-#255 auto-close.
# 3. If any remain open, re-open #215 and either (a) drop the suppression or
# (b) widen the filter -- not silently leave the alerts.…H parts
Two related concerns from review feedback on the fresh-interpreter pin:
1. Substring match on "ImportError" / "RecursionError" was too loose:
any benign log line, deprecation warning, or docstring snippet that
mentioned those substrings (e.g. an optional-dep warning saying
"...will raise ImportError on Python 3.13...") would have failed
the pin even though the import succeeded. Replaced both substring
asserts with a single MULTILINE regex anchored on the start-of-line
`ExceptionName:` framing that Python emits for an uncaught
traceback. Also extended the same check to the
test_full_triple_imports_in_one_process case, which previously had
no traceback assertion at all.
2. _subprocess_env() built PYTHONPATH as
"<sys.path joined> + os.pathsep + env.get('PYTHONPATH', '')"
which produced a trailing pathsep when the parent had no PYTHONPATH
set (the common CI case). POSIX interprets a trailing or interior
empty pathsep entry as the current working directory, silently
injecting `.` into the child's sys.path -- defeating the
documented "propagate sys.path, nothing else" intent and masking
regressions where a module is only importable because cwd happens
to contain it. Switched to building an explicit list and only
appending the inherited PYTHONPATH if non-empty.
All 4 tests in tests/simulation/test_no_cyclic_imports.py pass on
this checkout. No behaviour change to the import-graph invariant
itself; this commit only tightens what the pin actually pins.
Refs: review feedback on PR strands-labs#216 -- threads PRRT_kwDORUMiZs6Ergk8
and PRRT_kwDORUMiZs6Ergk_.
…audit reminder Two doc concerns from review feedback: 1. The interaction between workflow-level `queries:` (security-and-quality in codeql.yml) and config-file `query-filters` is subtle: a workflow `queries:` value replaces a config-file `queries:` value unless prefixed with `+`, while `query-filters` always layer on top. Added an explicit "Query suite vs config-file interaction" section confirming the active suite is the workflow-selected one and the config layers `query-filters` on top -- saves the next reader a round-trip to the CodeQL Action docs and pins the assumption for any future contributor who adds `queries:` to config.yml. 2. The documented assumption that the simulation triple is the only file shape firing this rule today drifts silently as the codebase grows. Added a "Periodic audit reminder" subsection with a reproducible CodeQL CLI recipe (drop the exclude, run UnsafeCyclicImport.ql locally, inspect SARIF for results outside the triple). Cadence guidance: every minor release or every six months, whichever is sooner. Also folded the regex-anchored traceback shape from the prior commit into the regression-contract bullet so the README stays consistent with what the pin actually asserts. No behaviour change. README only. Refs: review feedback on PR strands-labs#216 -- threads PRRT_kwDORUMiZs6ErglA and PRRT_kwDORUMiZs6ErglE.
yinsong1986
left a comment
There was a problem hiding this comment.
Summary
Config-only PR that suppresses CodeQL's py/unsafe-cyclic-import globally via a new .github/codeql/config.yml, wires it into both CodeQL workflows, drops breadcrumbs in the three affected source files, and adds tests/simulation/test_no_cyclic_imports.py as a CodeQL-independent regression pin. The runtime-safety argument (asymmetric, not closed; PEP 563 belt-and-braces) is sound and matches what the existing test_no_import_cycle.py already pins. No source behaviour changes.
What's good
- Both CodeQL workflows are wired to the same config (consistent surface).
- Pin tests are paired thoughtfully: static graph (
test_no_import_cycle.py) plus dynamic fresh-interpreter (test_no_cyclic_imports.py), which catches the two distinct regression shapes. - The R1/R2 round changelog in the description is unusually transparent about why the substring->regex tightening and PYTHONPATH filter exist; that documentation will pay off in a future bisect.
- AGENTS.md compliance: no new emojis on touched lines, no host paths, scope discipline (action-pinning of
codeql-advanced.ymlcorrectly deferred to #217 rather than expanded into this PR). - Documented cost of the global suppression and a periodic-audit recipe.
Concerns
- Suppression is global with only a soft mitigation. A future legitimate
py/unsafe-cyclic-importviolation in unrelated code is silently hidden. The README "every six months" audit reminder is honest, but nothing enforces it. Consider: a small CI check that runscodeql database analyze ... UnsafeCyclicImport.qlwithout the filter and asserts the result set equals exactly the simulation triple. Out of scope for this PR; mentioning so it's a tracked thought. - Workflow consistency drift.
codeql.ymlpinsgithub/codeql-actiontov3.29.4SHAs;codeql-advanced.yml(touched in this PR) still uses floating@v4tags. The two workflows now share the sameconfig.ymlbut run on different major CodeQL action versions, which can produce diverging filter behaviour. The PR description correctly defers SHA-pinning to #217, but the v3-vs-v4 major gap is worth resolving alongside #217 rather than a pure SHA pin. - Doc drift on line numbers. Several places (
config.ymllines 22-24,README.md, the PR description) citebase.py:50andpolicy_runner.py:51-54. After this PR's breadcrumbs are inserted, the actual locations arebase.py:66andpolicy_runner.py:52-54. Inline comment below. - Companion test reference.
test_no_import_cycle.py::test_base_does_not_lazy_import_policy_runneris referenced from bothconfig.ymlandREADME.md. Confirmed that test exists today, but the README/config will silently drift if the test name changes; anxfailimport-style cross-reference would be more robust. Out of scope; flagging.
Verification suggestions
# Pin tests pass on this checkout:
pytest tests/simulation/test_no_cyclic_imports.py tests/simulation/test_no_import_cycle.py -v
# YAML validates:
python3 -c 'import yaml; yaml.safe_load(open(".github/codeql/config.yml"))' && echo OK
# After-merge check on main: confirm the listed alerts (#83-#87, #253-#255)
# auto-close on the next CodeQL run, and that no NEW unrelated cyclic-import
# alerts are silently masked. Run the README's CLI recipe locally with the
# exclude block dropped to enumerate.…l not line
Two surgical fixes addressing review feedback on the R3 cycle. Out of
scope items deferred per AGENTS.md round budget (R3 is the cap on this
PR -- see PR description Pre-push checklist).
1. PYTHONPATH internal-empties filter (tests/simulation/test_no_cyclic_imports.py):
The R2 fix only filtered the *fully-empty* inherited PYTHONPATH case
("" -> trailing pathsep -> cwd injection). Inherited values with
internal empties (":foo", "foo:", "foo::bar") still leaked through
because the filter appended the inherited string verbatim.
Now: split the inherited value on os.pathsep and filter empty entries
individually before extending parts. Verified with synthetic
PYTHONPATH="foo::bar:baz::" -> no empty entries in the joined result.
The R2 docstring already promised "leading, trailing, or interior
empty pathsep entry" coverage, so this brings the implementation in
line with the documented contract.
2. Cite by symbol, not by line number (.github/codeql/README.md):
The R2 README cited base.py:50 / policy_runner.py:51-54 /
benchmark.py:46-47 as the locations of the cycle's runtime and
TYPE_CHECKING edges. The R0 breadcrumbs landed in this same PR
shifted those line numbers (base.py:50 is now the actual import at
:66; the others moved by similar small amounts). Hard-coded line
numbers in long-lived rationale comments are a maintenance liability
-- any future edit above the import silently invalidates the
citation.
Now: cite by the import's *symbol shape* (the top-level
"from ... import PolicyRunner, VideoConfig" statement and the
"if TYPE_CHECKING:" blocks). The same edits to the References
section preserve the historical alert line numbers (strands-labs#85, strands-labs#86, strands-labs#87
raised against benchmark.py:42 / policy_runner.py:49-50 at the SHA
when the alerts were filed) but reframe them explicitly as
point-in-time historical markers, not current-tree citations.
Out of scope (deferred):
- Workflow major-version inconsistency between codeql.yml (v3.29.4
SHA-pinned) and codeql-advanced.yml (floating @v4): tracked in strands-labs#217.
That issue covered SHA pinning; updating it to also resolve the
major-version drift is the right home for the unified fix.
- Stale Note (strands-labs#191) framing in base.py: the suppression note now
framing-overlaps with the existing comment block. Out of scope for
this PR since the existing comment is correct as a runtime-cycle
explainer; the new comment correctly frames CodeQL. A follow-up that
consolidates them belongs in a docs cleanup PR.
- CI enforcement that the suppression stays narrow: noted in README
(the manual periodic-audit recipe added in R2). A CI step is the
right long-run shape but out of scope for this config-only PR.
Verification:
- pytest tests/simulation/test_no_cyclic_imports.py -v --no-cov: 4 passed
- ruff check + ruff format --check on the touched test file: clean
- grep -nP '[^\x00-\x7F]' on touched files: no non-ASCII
- Synthetic PYTHONPATH='foo::bar:baz::' check: no empty entries leak
Pin contract preserved: the R2 traceback regex (anchored on start-of-line
ExceptionName: framing) is unchanged. The R3 PYTHONPATH change tightens
the hygiene of the subprocess env without weakening the failure-detection
shape.
Closes review thread on PYTHONPATH internal-empty leak.
Closes review thread on line-number drift in README citations.
Refs strands-labs#215 strands-labs#209 strands-labs#217
…missed in prior R3 commit) R3 addressed the line-number-drift concern by switching README.md to symbol-based citations, but missed the identical pattern in .github/codeql/config.yml lines 22-24. This commit completes that concern: base.py:50 -> base.py's top-level import statement; policy_runner.py:51-54 -> its TYPE_CHECKING block. No behavioural change -- YAML comment only. Config still parses identically (verified with yaml.safe_load + assertion). Refs strands-labs#215 strands-labs#217
yinsong1986
left a comment
There was a problem hiding this comment.
Summary
This PR suppresses py/unsafe-cyclic-import globally via a new .github/codeql/config.yml wired into both CodeQL workflows, plus per-file breadcrumbs in simulation/{base,policy_runner,benchmark}.py and a fresh-interpreter pin test (tests/simulation/test_no_cyclic_imports.py) that complements the existing static-graph pin. The runtime-safety argument (asymmetric cycle: base -> policy_runner runtime edge, back-edges only under TYPE_CHECKING) is correct, and I was able to verify both tests/simulation/test_no_cyclic_imports.py (4 passed) and tests/simulation/test_no_import_cycle.py (2 passed) on 963542b.
The diff is config + comments + tests; no behavioural change. The rationale documentation is unusually thorough (config inline + README + per-file breadcrumbs all pointing at the same regression contract).
What's good
- Two complementary pins (static graph via
simple_cycles, dynamic via fresh subprocess) guard the runtime invariant independent of CodeQL. This matches the AGENTS.md PR #85 review-learning "Pin regression tests for reviewed fixes". _subprocess_env()correctly handles the empty-pathsep -> cwd-injection trap on POSIX. The R2/R3 iteration on this is well-reasoned.- Traceback assertion is anchored on
^(ImportError|RecursionError):(MULTILINE) rather than substring, eliminating the obvious false-positive class. - Breadcrumbs in each affected source file explicitly say "Do NOT hoist the
TYPE_CHECKINGimport..." with the precise failure mode (partially initialized module) — exactly the right shape to deter the regression. - README's
## Periodic audit reminderwith a reproduciblecodeql database analyzerecipe is the right way to mitigate the global-vs-path-scoped tradeoff.
Concerns
- Action SHA-pinning is deferred to #217 but this PR edits the file. Per AGENTS.md > Review Learnings (PR #92) > Action Pinning, "All
uses:references in workflows pin to a full 40-character commit SHA... Especiallypypa/gh-action-pypi-publish-- ... This pin is non-negotiable."codeql-advanced.ymllines 60/70/102 still use@v4. Since this PR is already touching theinit:step in that file, the natural fix is to pin all three references in the same diff. Deferring to #217 leaves a documented AGENTS.md violation in a file this PR claims to have polished. - The R3 changelog asserts "All citations by symbol, not line number" but the README cites
codeql-advanced.yml:78-79. The cite happens to still resolve correctly today, but contradicts the stated convention. Inline below. - Verification suggestion: the suppression itself is unverifiable until CodeQL runs on
mainpost-merge. Worth confirming alerts #85, #86, #87, #253, #254, #255 actually auto-close; the PR description commits to that follow-up but it should be tracked. - Minor doc drift: PR description says "8 passed (4 + 1 + 2 + 1)" — actual count is 4 (parametrized fresh-interpreter) + 1 (combined) + 2 (existing graph + lazy-import) = 7 here. Inconsequential.
Verification suggestions
- Confirm pin tests pass on the head SHA:
Verified locally: 4 + 2 = 6 passed in ~12s.
python -m pytest tests/simulation/test_no_cyclic_imports.py tests/simulation/test_no_import_cycle.py -v
- After merge, inspect the Security tab on
main: alerts #85, #86, #87, #253, #254, #255 should auto-close on the next scheduled CodeQL run. If they don't, the YAML schema is silently wrong and the suppression is a no-op. - Spot-check the suppression actually layers on top of
security-and-quality: the README claim is that workflow-levelqueries:doesn't overridequery-filtersfrom the config file. Easy to verify post-merge by counting alert deltas across all rules, not just the suppressed one.
R3 changelog claimed 'All citations by symbol, not line number' but README still cited '.github/workflows/codeql-advanced.yml:78-79'. Replace with a symbol-based reference (the 'queries listed here will override any specified in a config file' comment block) so future edits above the import don't silently invalidate the pointer. Addresses review feedback on R3 self-contradiction.
yinsong1986
left a comment
There was a problem hiding this comment.
Summary
This PR globally suppresses CodeQL's py/unsafe-cyclic-import rule via a new .github/codeql/config.yml, wires the config into both codeql.yml and codeql-advanced.yml, and lands per-file breadcrumbs plus a fresh-interpreter pin test (tests/simulation/test_no_cyclic_imports.py). The runtime-safety argument for the asymmetric static-only cycle on the simulation triple is sound, the regression contract (this PR's new dynamic pin + the existing static-graph pin in test_no_import_cycle.py) is meaningful, and the README documents the trade-offs honestly. Schema-level YAML validates and the new pin tests pass locally (4 passed in 2.22s).
What's good
- Two-axis regression contract (static AST graph + dynamic fresh-interpreter import) catches both failure modes the suppression masks.
- The README is unusually honest about the cost of a global suppression and provides a reproducible audit recipe with CodeQL CLI commands.
- Per-file breadcrumbs point readers at the rationale without grep-spelunking, satisfying the "How to extend > step 3" rule the PR establishes.
- The R2 fix to anchor the stderr regex on
^(ImportError|RecursionError):(vs substring) is a real correctness improvement; the R3 fix to split inheritedPYTHONPATHinterior empties closes a concrete cwd-injection foot-gun. - ASCII clean across all touched files (AGENTS.md non-ASCII rule).
Concerns
- Mixed action-pinning hygiene in
codeql-advanced.yml. AGENTS.md > Review Learnings (#92) > Action Pinning is explicit: "Alluses:references in workflows pin to a full 40-character commit SHA" and calls out floating tags as "the supply-chain pattern that thetj-actions/changed-filesincident exploited". The diff lands new content next toactions/checkout@v4(line 60),github/codeql-action/init@v4(line 70), andgithub/codeql-action/analyze@v4(line 102). The PR author defers this to #217 to keep the diff config-only, which is a defensible scope call; the deferral is on the record. If #217 doesn't ship within a release cycle, the policy violation becomes harder to justify since this PR was the natural moment to fix it. codeql.ymlvscodeql-advanced.ymlmajor-version skew (v3 vs v4). Independent of pinning: the two workflows now share aconfig-file:input but disagree on the major version ofgithub/codeql-action/init(v3.29.4SHA vs@v4). If the v4 schema diverges from v3 in howquery-filtersare processed, the same config could behave differently on the two workflows. Worth confirming both still consume the filter identically.- Audit cadence is honor-system-only. The README's six-month / minor-release audit recipe is the only mitigation for the documented risk that a future legitimate
py/unsafe-cyclic-importviolation in unrelated code is silently suppressed. There is no CI check that fails when a new file outside the simulation triple would trigger the rule. A lightweight scheduled job that runs the CodeQL CLI recipe and opens an issue if results outside the triple appear would close the loop; otherwise the assumption drifts silently exactly as the README warns. Reasonable as a follow-up, not a blocker. config.ymlschema is not validated pre-merge. A typo in thequery-filtersshape (e.g.excludes:instead ofexclude:) would silently no-op until someone audits the Security tab. The PR notes this is covered by the post-merge alert-close audit, which is acceptable but worth surfacing.- Cross-reference accuracy in README. The historical alert references (lines 137-146) state "CodeQL alerts #83, #84 -- closed by PR #209 R4", but PR #209 is described as paused at draft and superseded by this PR; if #209 never merged, the closure attribution is suspect. Worth either verifying #83/#84 are actually closed (and by what commit) or rewording to "believed closed by the SHA at #209 R4 prior to pause".
Verification suggestions
# Confirm the YAML is structurally what CodeQL expects.
python3 -c 'import yaml; d = yaml.safe_load(open(".github/codeql/config.yml")); assert d["query-filters"][0]["exclude"]["id"] == "py/unsafe-cyclic-import"; print("OK")'
# Run the new pin (passes locally in 2.2s).
python3 -m pytest tests/simulation/test_no_cyclic_imports.py tests/simulation/test_no_import_cycle.py -v
# Spot-check that no other repo file would currently fire py/unsafe-cyclic-import
# (this is the audit recipe the README documents -- worth running once on this PR
# to validate the "only file shape in the repo today" claim that justifies the
# global suppression).
codeql database create /tmp/db --language=python --source-root=.
codeql database analyze /tmp/db codeql/python-queries:Imports/UnsafeCyclicImport.ql \
--format=sarif-latest --output=/tmp/cyclic.sarif
jq '.runs[].results[].locations[].physicalLocation.artifactLocation.uri' /tmp/cyclic.sarif | sort -u
# Expect: only paths under strands_robots/simulation/{base,policy_runner,benchmark}.py.
# Post-merge: confirm alerts #85, #86, #87, #253, #254, #255 auto-close on the
# next CodeQL run on main, and that no new alerts in unrelated files were
# pre-existing-and-now-silently-hidden.
yinsong1986
left a comment
There was a problem hiding this comment.
Summary
Suppresses CodeQL's py/unsafe-cyclic-import for the deliberate, asymmetric static-only cycle between simulation/{base,policy_runner,benchmark}.py. Wires both codeql.yml and codeql-advanced.yml to a new .github/codeql/config.yml, adds top-of-file breadcrumbs explaining why the rule is suppressed and what would re-introduce the runtime bug, and pins the runtime-safety invariant with two complementary regression tests (fresh-interpreter dynamic check + AST static-graph check). Companion PR #234 handles SHA-pinning + v3->v4 alignment of codeql-advanced.yml; explicitly out-of-scope here.
What's good
- The CodeQL-independent regression contract is the right shape: one test (
test_no_cyclic_imports.py) catches dynamic failures, the other (test_no_import_cycle.py) catches static-graph regressions. Together they make the suppression survivable if CodeQL is ever wrong. - Anchoring the stderr regex on
^(ImportError|RecursionError):(MULTILINE) instead of substring-matching is the right call — addresses the R2 false-positive class explicitly. _subprocess_env()filters emptyPYTHONPATHentries individually after split (R3 fix); the cwd-injection foot-gun is closed properly, not just for the empty-string case.- README documents the global-vs-path-scoped trade-off honestly and bundles a periodic-audit recipe so the assumption ("only file shape that fires today") is auditable rather than load-bearing-on-trust.
- Per-file breadcrumbs explicitly call out the failure mode ("do NOT hoist the TYPE_CHECKING import") so the next reader knows what NOT to do, not just what's true today.
- Scope discipline: SHA-pinning + major-alignment punted to #234 with a clear pointer; this PR stays narrow and reviewable.
Must fix before merge
(none — PR is ready to merge once any follow-ups are tracked)
The codeql-advanced.yml floating tags (@v4 for checkout/init/analyze) violate AGENTS.md > Review Learnings (#92) > Action Pinning, but the PR description scopes that fix to companion PR #234 and the file is fully wired to the new config-file: here. Treating the floating-tag concern as a blocker for #216 would couple this merge to #234 unnecessarily; the suppression itself is independently shippable. Verify #234 lands before or with #216 so main is never in the floating-tag-on-main state.
Follow-up in v0.4.1
- CI enforcement of the global-suppression invariant (
.github/codeql/README.md:109and.github/codeql/config.yml). The README warns that a future legitimate violation would be silently hidden and prescribes a manual periodic audit. A scheduled workflow that re-runs the rule with the suppression dropped (or a SARIF diff job) would surface drift without depending on a maintainer remembering. Track as a follow-up issue with the bash recipe fromREADME.md:117-127as the starting script. - Consolidate the two adjacent
py/unsafe-cyclic-importrationales instrands_robots/simulation/base.py(line 28-66 vs. the olderNote (#191):block at line 58). Both blocks now explain the same rule from slightly different angles; readability would improve from a single block. Cosmetic; defer. - Schema-validate
.github/codeql/config.ymlin CI so a typo inquery-filters(e.g.exlude:instead ofexclude:) fails fast rather than silently disabling the suppression. CodeQL itself emits a warning, not an error, on unknown keys. Track as a follow-up. - Cross-reference accuracy in README: the alert-history section lists
#83-#87, #253-#255as closed by this suppression but a sibling line says#83, #84were already closed by PR #209 R4. Reconcile so the closure record is unambiguous when the post-merge AC gate runs. Doc-only; defer.
Verification suggestions
# YAML validates and the suppression key is what we expect:
python3 -c 'import yaml; c = yaml.safe_load(open(".github/codeql/config.yml")); assert c["query-filters"][0]["exclude"]["id"] == "py/unsafe-cyclic-import"; print("OK")'
# Both pin tests pass on this checkout:
hatch run test tests/simulation/test_no_cyclic_imports.py tests/simulation/test_no_import_cycle.py -v
# Fresh-interpreter pin actually fails when the cycle is closed (sanity-check the pin):
# Temporarily hoist `from strands_robots.simulation.base import SimEngine` out of the
# TYPE_CHECKING block in policy_runner.py, rerun the test above, confirm RED, revert.
# After merge: confirm CodeQL alerts #85-#87 and #253-#255 transition to Closed in the Security tab.| # Refs: PR #209 (paused, full constraint analysis in S13/R6); issue #215 | ||
| # (this suppression); CodeQL alerts #83-#87, #253-#255 (the recurrent | ||
| # alerts the suppression closes). | ||
| - exclude: |
There was a problem hiding this comment.
[FOLLOW-UP] Global suppression is justified in the README given today's repo shape, but there is no CI enforcement that the assumption ("simulation triple is the only file shape that fires this rule") still holds at any future commit. The audit recipe at README.md:117-127 is manual and load-bearing on a maintainer remembering it every six months. Track a follow-up to add a scheduled workflow (or weekly cron) that runs the rule with the suppression temporarily dropped and opens an issue if results outside the simulation triple appear. Not a blocker — the regression pins guard the runtime invariant independently — but the silent-suppression risk is real if the codebase grows.
No justification for MUST FIX: the trade-off is documented, the runtime contract is pinned, and the surfaceable risk (a brand-new legitimate violation slips through) is bounded by the manual recipe today.
| build-mode: ${{ matrix.build-mode }} | ||
| # Repo-level query filters (suppress documented false-positives). | ||
| # See .github/codeql/README.md for per-rule rationale. | ||
| config-file: ./.github/codeql/config.yml |
There was a problem hiding this comment.
[FOLLOW-UP] This file is now wired to the new config-file:, but the surrounding init@v4 (line 70), analyze@v4 (line 102), and actions/checkout@v4 (line 60) are still floating tags, violating AGENTS.md > Review Learnings (#92) > Action Pinning ("All uses: references in workflows pin to a full 40-character commit SHA"). PR description scopes the SHA-pin to companion PR #234, which is the right call to keep this PR narrow.
Not a MUST FIX for #216 because (a) the floating tags pre-exist this PR — this diff only adds the config-file: block, it does not introduce the floating tags, and (b) #234 explicitly addresses it. Worth confirming #234 lands before or together with #216 so main is never in the floating-tag-on-@v4 state for longer than necessary.
| suppression and fix the new cycle properly rather than extend the | ||
| filter. | ||
|
|
||
| **Periodic audit reminder:** the assumption that the simulation triple |
There was a problem hiding this comment.
[FOLLOW-UP] The "every minor release or every six months" cadence has no enforcement mechanism. After v0.4.0 ships at Summit, this becomes a calendar item that reliably falls off — the exact failure mode that turns documented trade-offs into silent regressions. Pair with the CI-enforcement follow-up on config.yml: either a scheduled workflow that runs the recipe at README.md:117-127 automatically and opens an issue on drift, or a make audit-codeql-suppressions target invoked from a quarterly cron. Track as a v0.4.1 issue.
| @@ -26,11 +26,34 @@ | |||
| if TYPE_CHECKING: | |||
| from strands_robots.policies import Policy | |||
|
|
|||
There was a problem hiding this comment.
[FOLLOW-UP] This new top-of-file CodeQL block and the older Note (#191): block immediately below (line 58 in the head version, in this hunk's trailing context) both explain py/unsafe-cyclic-import and the TYPE_CHECKING cycle, from slightly different angles. After this PR lands, a future reader has to read two adjacent comment blocks to assemble the full rationale, and the OnFrame-specific guidance in the #191 block is functionally a corollary of the rule explained here.
Consolidating into a single block (with the OnFrame example as a sub-bullet) would tighten the breadcrumb without losing information. Cosmetic; defer to v0.4.1.
yinsong1986
left a comment
There was a problem hiding this comment.
Summary
This PR adds a CodeQL query-filters suppression for py/unsafe-cyclic-import against the simulation triple (base/policy_runner/benchmark), wires .github/codeql/config.yml into both codeql.yml and codeql-advanced.yml, drops per-file breadcrumb comments pointing at the rationale, and adds a fresh-interpreter regression pin (tests/simulation/test_no_cyclic_imports.py) that complements the existing static AST-graph pin. The runtime-safety argument (asymmetric cycle: base -> policy_runner is the only module-level edge; the back-edges live under TYPE_CHECKING) is sound and is documented thoroughly in .github/codeql/README.md. Cross-references to test_no_import_cycle.py::test_no_runtime_import_cycles and ::test_base_does_not_lazy_import_policy_runner are accurate.
What's good
- Scope discipline: pure config + breadcrumbs + tests, no production-code behaviour change.
- The two-pin contract (static AST graph + fresh-interpreter dynamic) is exactly the right shape for the failure modes called out (
ImportError: cannot import name ... from partially initialized module ...). - R2's tightening of the traceback regex to anchor on
^(ExceptionName):(instead of substring matching) and R3's per-entry filtering of inheritedPYTHONPATHempties were both correct calls. - README documents the
queries:vsquery-filtersinteraction explicitly and warns future contributors not to addqueries:toconfig.ymlwithout a+prefix in the workflow. - AGENTS.md > Review Learnings (#92) > "Pin every reviewed fix with a regression test" is satisfied for the runtime-safety invariant.
Must fix before merge
(none -- PR is ready to merge once any follow-ups are tracked)
This PR introduces no new public API, no wire format, no persisted-state schema, no CLI flag, no default-behaviour change, and no security regression. The one supply-chain hygiene concern in scope (floating @v4 tags in codeql-advanced.yml) is pre-existing, not introduced here, and the PR description openly defers it to companion PR #234. Per the bucket-default rule ("if you can't articulate a specific reason this blocks v0.4.0, classify FOLLOW-UP"), this is FOLLOW-UP.
Follow-up in v0.4.1
codeql-advanced.ymlfloating tags --actions/checkout@v4,github/codeql-action/init@v4,github/codeql-action/analyze@v4violate AGENTS.md > Review Learnings (#92) > "Action Pinning". Companion PR #234 is the tracked fix; verify it lands before / alongside this one.- No CI guard that the suppression list and breadcrumbs stay in sync. The README's "How to extend" recipe (
.github/codeql/README.md:148) is contributor guidance, not enforcement -- a future suppression added toconfig.ymlwithout the matching breadcrumb / README section will not fail any test. A small AST/regex test ("everyid:inconfig.ymlhas a matching## Suppressed queriesH3 in README and a# CodeQL:breadcrumb in at least one source file") would close the loop. R4 acknowledged this as deferred. config.ymlschema is not validated in CI. A typo'did:field (e.g.py/unsafe-cyclic-importsplural) silently disables the suppression with no test failure -- the runtime-pin tests would still pass. Ayaml.safe_load+ minimal-shape assertion (or, better, the upstream CodeQL schema if available) would catch this.- Traceback regex robustness.
_TRACEBACK_EXC_PATTERN = r"^(ImportError|RecursionError):"(tests/simulation/test_no_cyclic_imports.py:59) is tighter than the original substring match but still matches a printed multi-line docstring or log message that happens to start a line withImportError: .... Combining the regex with a precedingTraceback (most recent call last):line check would be more robust without sacrificing the legitimate-traceback signal. _subprocess_env()propagates the full parent environment.os.environ.copy()carries everything (CI tokens, locale knobs, debug flags). For an isolated import test, an explicit allowlist (e.g.PATH,PYTHONPATH,HOME,LANG,VIRTUAL_ENV) would be more hygienic and matches AGENTS.md > Review Learnings (#86) > "Reject silently-dropped kwargs" / explicit-contract spirit. R4 also flagged this as deferred.- Doc-drift risk on historical line-number cites. README's CodeQL alert references (
benchmark.py:42,policy_runner.py:49-50,base.py:28) are pinned to a SHA-at-the-time and will silently rot. Either drop the line numbers (the alert IDs are sufficient) or compute them fromgit blameat audit time -- noted in R4 as nit-scope.
Verification suggestions
# Pin tests pass on this checkout:
hatch run test tests/simulation/test_no_cyclic_imports.py tests/simulation/test_no_import_cycle.py -v
# Config parses as YAML:
python3 -c 'import yaml; print(yaml.safe_load(open(".github/codeql/config.yml")))'
# Per-file breadcrumb still references the right config path:
grep -n 'codeql/config.yml' strands_robots/simulation/{base,policy_runner,benchmark}.py
# After merge: confirm CodeQL alerts #85-#87 and #253-#255 transition to closed.| build-mode: ${{ matrix.build-mode }} | ||
| # Repo-level query filters (suppress documented false-positives). | ||
| # See .github/codeql/README.md for per-rule rationale. | ||
| config-file: ./.github/codeql/config.yml |
There was a problem hiding this comment.
[FOLLOW-UP] This PR wires config-file: correctly, but the surrounding action references on this workflow are still floating tags (actions/checkout@v4 line 60, github/codeql-action/init@v4 line 70, github/codeql-action/analyze@v4 line 102). That violates AGENTS.md > Review Learnings (#92) > 'Action Pinning' ("All uses: references in workflows pin to a full 40-character commit SHA") and is the supply-chain pattern that the tj-actions/changed-files incident exploited. The PR description openly defers this to companion PR #234, which is the right call -- not introduced by this PR, and out of scope for the suppression fix. Tracking only: confirm #234 lands before / alongside this PR so the gap is not left open after merge.
| # (this suppression); CodeQL alerts #83-#87, #253-#255 (the recurrent | ||
| # alerts the suppression closes). | ||
| - exclude: | ||
| id: py/unsafe-cyclic-import |
There was a problem hiding this comment.
[FOLLOW-UP] No CI guard validates this file's schema or that the id: here matches a real CodeQL rule. A typo (e.g. py/unsafe-cyclic-imports plural) silently disables the suppression -- the runtime-pin tests in tests/simulation/test_no_cyclic_imports.py would still pass because they exercise import behaviour, not CodeQL semantics, so the regression contract has a blind spot here. Suggested follow-up: add a tiny tests/test_codeql_config.py that yaml.safe_loads this file and asserts each exclude.id matches the regex ^[a-z]+/[a-z0-9-]+$ (CodeQL rule-id shape). Doesn't block merge; tracks under v0.4.1.
| recover mypy type info; closed by this suppression. (Historical line | ||
| number: `base.py:28` at the SHA that raised the alerts.) | ||
|
|
||
| ## How to extend |
There was a problem hiding this comment.
[FOLLOW-UP] The 'How to extend' recipe is contributor guidance, not enforcement -- a future suppression added to config.yml without the matching ## Suppressed queries README section or per-file breadcrumb will not fail any test. AGENTS.md > Review Learnings (#86) > 'Public API Hygiene' / 'centralise checks' suggests promoting this to a small lint test ("every exclude.id in config.yml has a matching H3 here and at least one source-file breadcrumb mentioning the id"). Track for v0.4.1; not blocking, since the suppression list is a single entry today.
| # Python 3.13...") would otherwise fail the pin even though the import | ||
| # itself succeeded. The traceback frame is the only stderr shape that | ||
| # *actually* indicates the failure mode this pin exists to catch. | ||
| _TRACEBACK_EXC_PATTERN = re.compile(r"^(ImportError|RecursionError):", re.MULTILINE) |
There was a problem hiding this comment.
[FOLLOW-UP] R2's tightening from substring to ^(ExceptionName): MULTILINE is a real improvement, but the regex still matches a printed docstring or log-line whose leading text happens to be ImportError: ... (e.g. an optional-dep warning that the suppressed module emits during import on certain Python versions). Tightening to also require a preceding Traceback (most recent call last): line within the previous N lines, or matching against the well-known traceback-frame shape ( File "...", line N, in ... immediately above), would close the residual ambiguity without weakening the legitimate-traceback signal. Edge-case; the runtime-cycle failure mode this pin guards against still surfaces correctly under the current regex. Track for v0.4.1.
| into the child's ``sys.path`` and could mask a regression where a | ||
| module is only importable because ``cwd`` happens to contain it. | ||
| """ | ||
| env = os.environ.copy() |
There was a problem hiding this comment.
[FOLLOW-UP] os.environ.copy() propagates the entire parent environment to the child interpreter (CI tokens, debug flags, locale knobs, STRANDS_* overrides). For an isolated fresh-interpreter import test the principled contract is an explicit allowlist (PATH, PYTHONPATH, HOME, LANG, VIRTUAL_ENV, SYSTEMROOT on Windows). Matches AGENTS.md > Review Learnings (#86) > 'Reject silently-dropped kwargs' / explicit-contract spirit -- right now an unrelated STRANDS_ROBOT_MODE=real in the parent env would silently flow into the child and could cause surprising behaviour the day a future suppressed module reads it at import time. R4 already triaged this as deferred; track for v0.4.1.
security(codeql): suppress py/unsafe-cyclic-import on simulation triple (closes #215)
1. Problem
Two separate but coupled concerns:
Floating tags in
codeql-advanced.yml. Threeuses:references still pin to floating@v4tags, violating AGENTS.md > Review Learnings (PR improve: enforce STRANDS_TRUST_REMOTE_CODE gate in policy factory #92) > Action Pinning: "Alluses:references in workflows pin to a full 40-character commit SHA, with the version tag preserved as a trailing comment." This is the same supply-chain pattern exploited in thetj-actions/changed-filesincident.Major-version drift between sister workflows. After security(codeql): suppress py/unsafe-cyclic-import on simulation triple (closes #215) #216 landed, both
codeql.ymlandcodeql-advanced.ymlshould run on the same major ofgithub/codeql-action, but they pinned different majors:codeql.yml:init@4e828ff8.../analyze@4e828ff8...(v3.29.4)codeql-advanced.yml:init@v4/analyze@v4(floating)Different majors can produce divergent
query-filterssemantics or different SARIF post-processing.2. Solution
This PR handles only the CodeQL config suppression and its regression contract. The SHA-pinning + major-alignment of
codeql-advanced.ymlis handled by the companion PR #234 (ci: pin codeql-advanced.yml action SHAs and align CodeQL major), which landed after this PR was filed.3. What this PR delivers
.github/codeql/config.yml—query-filters: [{exclude: {id: py/unsafe-cyclic-import}}].github/codeql/README.md— full rationale, threat-model, periodic-audit recipe, historical alert trackercodeql.ymlandcodeql-advanced.ymlwired toconfig-file: ./.github/codeql/config.ymlsimulation/{base,policy_runner,benchmark}.pytests/simulation/test_no_cyclic_imports.py— fresh-interpreter regression pin (4 parametrised tests)tests/simulation/test_no_import_cycle.py— static AST-graph pin (unchanged)4. Files changed
.github/codeql/config.ymlpy/unsafe-cyclic-importglobally.github/codeql/README.md.github/workflows/codeql.ymlconfig-file:.github/workflows/codeql-advanced.ymlconfig-file:strands_robots/simulation/base.pystrands_robots/simulation/policy_runner.pystrands_robots/simulation/benchmark.pytests/simulation/test_no_cyclic_imports.py5. Acceptance criteria from #215
py/unsafe-cyclic-importsuppressed globally viaquery-filtersTYPE_CHECKINGimportscodeql-advanced.yml— addressed by PR ci: pin codeql-advanced.yml action SHAs and align CodeQL major (closes #217) #2346. Companion PRs
ci: pin codeql-advanced.yml action SHAs and align CodeQL major) — Addresses the SHA-pinning + v3->v4 alignment concern raised repeatedly in R2-R5 of this PR. All threads on that topic are resolved by ci: pin codeql-advanced.yml action SHAs and align CodeQL major (closes #217) #234..github/workflows/dependabot.ymlpath fix (Dependabot ignores current location)7. Verification
S13 -- Review Round Changelog
_lazy_policy_runner()(nonexistent). Pin test path wrong. Config comment self-contradicting.00cf48atests/simulation/test_no_cyclic_imports.py(4 passed)963542b^(ExcName):MULTILINE":foo"/"foo::bar".eaf1c57_subprocess_env()now splits and filters inherited entries individuallyeaf1c57; (c) 9 threads are nits/follow-up scope (doc-drift line-number cites, assertion robustness, env-propagation allowlist, schema validation CI, global-suppression enforcement CI, stale Note(#191) consolidation, cross-reference accuracy). Follow-ups filed as appropriate.Autonomous agent submission. Strands Agents. Feedback to @cagataycali.