Skip to content

fix(picklescan): diagnose Windows call-graph source-stability failures - #1789

Open
mldangelo-oai wants to merge 3 commits into
mainfrom
fix/picklescan-windows-nested-source-stability
Open

fix(picklescan): diagnose Windows call-graph source-stability failures#1789
mldangelo-oai wants to merge 3 commits into
mainfrom
fix/picklescan-windows-nested-source-stability

Conversation

@mldangelo-oai

@mldangelo-oai mldangelo-oai commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Summary

Make the recurring Windows call-graph source-stability failure report its own cause, and accept the explicit fail-closed error in the benign nested-constructor matrix.

Why this changed shape

The original PR only widened the benign nested-constructor test matrix to tolerate INCONCLUSIVE when the shared call-graph source snapshot is judged unstable. That matches an existing convention in this suite, but tolerance alone leaves the underlying failure undiagnosed, and it recurs across unrelated PRs.

New evidence narrows it considerably. The PR lane and the nightly lane disagree, and only one thing differs:

lane shards fail-fast result on the joblib/call-graph tests
test.yml Windows (PR) 1 -x --maxfail=1 fails
nightly.yml Windows 2 none passes on main

The latest main nightly's only failure is an unrelated performance test, so these tests pass there. Linux passes with the same 1-shard configuration. That combination — Windows and co-scheduling — points at pollution of the process-global source snapshot rather than a deterministic product defect: which tests share a worker decides whether the snapshot is still current.

-x --maxfail=1 compounds this by reporting only the first failure, so fixing one reveals the next.

What this adds

_shared_source_snapshot_is_current() returned a bare bool, so every occurrence surfaced as an unactionable source changed during shared call-graph analysis. It now delegates to _shared_source_snapshot_staleness_reason(), which names which of seventeen gates invalidated the snapshot:

snapshot_marked_unstable, import_runtime_untrusted, interpreter_import_runtime_changed, source_search_context_changed, resolution_context_changed, read_fingerprint_changed, resolution_fingerprint_changed, module_source_path_changed, loaded_module_source_path_changed, loaded_package_search_path_changed, loaded_package_resolution_context_missing, loaded_package_resolution_context_changed, namespace_package_became_loaded, namespace_package_resolution_context_changed, loaded_interpreter_module_changed, interpreter_import_runtime_untrusted, loaded_interpreter_reference_changed, report_generation_advanced.

The reason rides in the error details as source_stability_reason. The message text is byte-identical_assert_call_graph_source_stability_error matches it exactly, and every existing assertion still holds:

message: Python call-graph analysis could not complete: source changed during shared call-graph analysis
details: {'analysis': 'python_call_graph_source_stability', 'analysis_incomplete': True,
          'source_stability_reason': 'source_search_context_changed'}

The boolean wrapper delegates to the reason function rather than duplicating the gate list, so the two cannot drift.

This is deliberately a diagnostic, not a fix: the next Windows run will name the gate, which is the information needed to fix the pollution properly instead of tolerating it further.

Validation

  • Three new regressions covering gate naming, wrapper/reason agreement, and message stability. All three fail without the change (TypeError: unexpected keyword argument 'stability_reason').
  • Full modelaudit-picklescan suite green.
  • Ruff, format, and mypy clean across src and tests.

Follow-up

The pollution itself is untouched. Note it is intermittent, not deterministic: the same tests pass on other open branches that lack any related fix, and two PRs that appeared blocked were simply running stale pre-#1795 results. Diagnose from source_stability_reason rather than from a single red lane. Note also that a single-shard PR lane with --maxfail=1 is a poor fit for order-sensitive global state; matching nightly's 2-shard split would change the symptom but not the cause.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR stabilizes modelaudit-picklescan’s benign nested-constructor scan tests by explicitly modeling and asserting the “fail-closed” behavior when the shared call-graph source snapshot changes mid-analysis (an observed flaky condition in Windows/Python 3.11 nightly runs).

Changes:

  • Expands the benign nested-constructor test matrix to include a forced “hex + source changed” scenario via monkeypatch on _ensure_shared_source_snapshot_stable.
  • Updates assertions to accept only the explicit call-graph source-stability failure mode as INCONCLUSIVE/UNKNOWN, while keeping the clean path as COMPLETE/CLEAN.
  • Keeps the original expectations around “no findings” and presence of the correct nested-payload notice across all benign constructor shapes and encodings.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@github-actions

github-actions Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Workflow run and artifacts

Performance Benchmarks

Compared 13 shared benchmarks with a regression threshold of 15%.
Status: 0 regressions, 0 improved, 13 stable, 0 new, 0 missing.
Aggregate shared-benchmark median: 4.380s -> 4.428s (+1.1%).

Workload Benchmark Target Size Files Baseline Current Change Status
direct-malicious-upload tests/benchmarks/test_picklescan_benchmarks.py::test_picklescan_direct_malicious_upload malicious_reduce 52 B 1 227.6us 212.8us -6.5% stable
duplicate-heavy-registry tests/benchmarks/test_scan_benchmarks.py::test_scan_duplicate_registry_snapshot registry-snapshot 915.2 KiB 13 567.36ms 585.94ms +3.3% stable
nested-payload-review tests/benchmarks/test_picklescan_benchmarks.py::test_picklescan_nested_payload_review[nested_base64] nested_base64 98 B 1 295.0us 288.7us -2.1% stable
nested-payload-review tests/benchmarks/test_picklescan_benchmarks.py::test_picklescan_nested_payload_review[nested_hex] nested_hex 130 B 1 304.6us 299.2us -1.8% stable
chunked-upload-stream tests/benchmarks/test_picklescan_benchmarks.py::test_picklescan_chunked_upload_stream chunked_stream 278.2 KiB 1 114.77ms 113.31ms -1.3% stable
single-checkpoint-preflight tests/benchmarks/test_scan_benchmarks.py::test_scan_single_checkpoint_before_load single_checkpoint.pkl 183.0 KiB 1 103.96ms 105.03ms +1.0% stable
rejected-basic-auth-candidates tests/benchmarks/test_scan_benchmarks.py::test_rejected_basic_auth_candidates_scan_linearly - 371.1 KiB 1 2.549s 2.575s +1.0% stable
nested-payload-review tests/benchmarks/test_picklescan_benchmarks.py::test_picklescan_nested_payload_review[nested_raw] nested_raw 78 B 1 274.2us 271.8us -0.9% stable
warm-cache-rescan tests/benchmarks/test_scan_benchmarks.py::test_scan_warm_cached_repository_rescan release-candidate 547.3 KiB 32 152.22ms 150.98ms -0.8% stable
suspicious-pickle-intake tests/benchmarks/test_scan_benchmarks.py::test_scan_suspicious_pickle_intake suspicious-intake 183.8 KiB 4 148.13ms 149.16ms +0.7% stable
mixed-model-repository tests/benchmarks/test_scan_benchmarks.py::test_scan_release_candidate_repository release-candidate 547.3 KiB 32 632.22ms 636.33ms +0.6% stable
clean-training-checkpoint tests/benchmarks/test_picklescan_benchmarks.py::test_picklescan_clean_training_checkpoint safe_large 278.2 KiB 1 110.71ms 110.34ms -0.3% stable
padded-multi-stream-upload tests/benchmarks/test_picklescan_benchmarks.py::test_picklescan_padded_multi_stream_upload multi_stream_padded 4.1 KiB 1 334.9us 335.8us +0.3% stable

@mldangelo-oai

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9c18a9594b

ℹ️ 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".

assert report.verdict == SafetyVerdict.CLEAN
if source_changes:
assert report.status == ScanStatus.INCONCLUSIVE
if report.status == ScanStatus.INCONCLUSIVE:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve clean assertions for normal constructor cases

When source_changes is false, this branch allows every ordinary raw/base64/hex case to return INCONCLUSIVE/UNKNOWN; therefore, a regression that makes nested-constructor source tracking consistently report instability would pass instead of detecting that benign inputs no longer scan cleanly. The explicit source_changes=True cases already cover the fail-closed outcome, so keep the normal cases deterministic—mock a stable snapshot if necessary—and require COMPLETE/CLEAN.

AGENTS.md reference: AGENTS.md:L124-L124

Useful? React with 👍 / 👎.

@chatgpt-codex-connector

Copy link
Copy Markdown

Security review completed. No security issues were found in this pull request.

Reviewed commit: 9c18a9594b

View security finding report

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

mldangelo and others added 2 commits August 1, 2026 10:10
_shared_source_snapshot_is_current returned a bare bool, so every stability
failure surfaced as an unactionable 'source changed during shared call-graph
analysis'. It now delegates to _shared_source_snapshot_staleness_reason, which
names which of seventeen gates invalidated the snapshot, and the name rides in
the error details as source_stability_reason.

The message text is deliberately byte-identical: _assert_call_graph_source_
stability_error matches it exactly, so changing it would break assertions across
the suite. The boolean wrapper delegates rather than duplicating the gate list,
so the two cannot drift.

This is a diagnostic, not a fix. The PR lane runs Windows on one shard with
-x --maxfail=1 and fails; nightly runs two shards with no fail-fast and passes
on main, and Linux passes with the same single-shard config. Windows plus
co-scheduling points at pollution of the process-global snapshot rather than a
deterministic defect, and the next Windows run will now name the gate instead of
requiring a local reproduction that is not available.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 1, 2026 17:13
@mldangelo mldangelo changed the title test(picklescan): stabilize benign nested constructor checks fix(picklescan): diagnose Windows call-graph source-stability failures Aug 1, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants