Skip to content

chore: sync upstream NVIDIA/SkillSpector 2.8.1 (inference usage telemetry) - #13

Merged
will-exaforce merged 34 commits into
mainfrom
wbeasley/sync-upstream-2.8.1
Aug 11, 2026
Merged

chore: sync upstream NVIDIA/SkillSpector 2.8.1 (inference usage telemetry)#13
will-exaforce merged 34 commits into
mainfrom
wbeasley/sync-upstream-2.8.1

Conversation

@will-exaforce

Copy link
Copy Markdown

Summary

  • Merge upstream NVIDIA/SkillSpector release 2.8.1 (0a1546b) into main — the oldest release that ships LLM token-usage telemetry (metadata.inference_usage in JSON reports, see docs/INFERENCE_USAGE.md).
  • Also brings the 2.5.x–2.8.x line: inspection ledger, LLM batch retry rework, MCP registry posture scanning, shared Python AST parsing, and static-analyzer fixes.
  • Clean merge: zero conflicts, no fork-side edits; src/tests remain byte-identical to the upstream snapshot apart from the existing src/skillspector/exaforce/ patch module.

Details

No structured plan — upstream snapshot sync, no fork-authored code changes.

Tests: full suite uv run pytest -q on this branch: 1917 passed, 13 skipped, 4 failed. The 4 failures are pre-existing on main (verified by running the same tests in a clean worktree at main) and are by design: the fork's exaforce schema patches prune LLM prose fields (explanation, remediation, intent, impact, overall_assessment) that these upstream tests assert (test_to_finding, test_model_dump, test_intent_validation in tests/nodes/test_llm_analyzer_base.py; test_malicious_skill_findings_preserve_metadata in tests/nodes/test_semantic_quality_policy.py). The PatchDriftError guard did not fire — fork patches apply cleanly against 2.8.1.

Note: pre-commit (ruff, config at upstream parity) flags 21 issues in upstream's own contrib/batch_scan/ files. Left untouched deliberately — reformatting upstream-tracked files would break parity and cause conflicts on the next sync.

keshprad and others added 30 commits July 21, 2026 14:37
AnthropicProvider ignored ANTHROPIC_BASE_URL and always constructed
ChatAnthropic against the hardcoded https://api.anthropic.com, so proxy
and gateway deployments were silently bypassed. This is inconsistent with
OpenAIProvider, which already honors OPENAI_BASE_URL.

Read ANTHROPIC_BASE_URL in resolve_credentials() and pass it through in
create_chat_model(), falling back to api.anthropic.com when unset --
mirroring the OpenAIProvider pattern.

Tests:
- Add unit coverage for the base_url override and the default-endpoint
  fallback (resolve_credentials + create_chat_model).
- Clear ANTHROPIC_BASE_URL in the provider/llm_utils env-isolation
  fixtures now that the provider reads it, keeping tests hermetic.
- Clear ANTHROPIC_BASE_URL in the live anthropic endpoint test so it
  validates the default URL, mirroring the OpenAI live test.

Signed-off-by: Papastavros Vaggelis <psvaggelis@gmail.com>
Signed-off-by: Keshav Pradeep <32313895+keshprad@users.noreply.github.com>
…e-url

fix(anthropic): honor ANTHROPIC_BASE_URL env override
Signed-off-by: Rod Boev <rod.boev@gmail.com>
fix(provider): isolate Claude CLI settings hooks with --setting-sources=
Signed-off-by: keshavp <32313895+keshprad@users.noreply.github.com>
Re-applies NVIDIA#282, reverted on main by the 2.4.3 OSS snapshot sync
(a54947c). resolve_credentials() now reads ANTHROPIC_BASE_URL and
create_chat_model() uses base_url or the default endpoint, so
proxy/gateway deployments route through the configured host again.
Regression coverage for the override and default paths restored.

Signed-off-by: Papastavros Vaggelis <psvaggelis@gmail.com>
* chore(release): publish 2.4.4 snapshot

Publish the public 2.4.4 package metadata and changelog entry for the internal import of GitHub PR 301. Preserve existing GitHub workflows and public provider tests.

Verification: make lint; make format-check; make test-unit; git diff --check origin/main.
Signed-off-by: Keshav Pradeep <32313895+keshprad@users.noreply.github.com>

* feat(release): publish GitHub releases from labeled PRs

Refresh the existing OSS snapshot with the public label-gated release workflow and preserve public changelog source titles.

Signed-off-by: Keshav Pradeep <32313895+keshprad@users.noreply.github.com>

---------

Signed-off-by: Keshav Pradeep <32313895+keshprad@users.noreply.github.com>
Publish the sanitized 2.5.0 public snapshot with execution ledger and completeness reporting.\n\nThis retains public GitHub workflows, public documentation, and the versioned release notes while excluding internal release tooling, provider implementation, and implementation-planning documents.\n\nVerification: the current internal main pipeline passed lint, unit, integration, Docker smoke, and Sonar. Snapshot integrity and diff checks passed; local tests were not rerun per release guidance.

Signed-off-by: Keshav Pradeep <32313895+keshprad@users.noreply.github.com>
…IDIA#305)

`arun_batches` bursts up to a hardcoded `max_concurrency=10`. On a rate-limited
provider (free tiers with a low RPM) that burst guarantees 429s, and 429'd
batches are dropped from the result — silently losing analyzer coverage,
including the security-critical `semantic_security_discovery` batch, while the
report still renders a full risk_assessment.

Add `SKILLSPECTOR_MAX_LLM_CONCURRENCY` so users can serialize the fan-out
(set it to 1) and stay under the provider's rate limit. The default stays 10;
an explicit `max_concurrency=` argument still wins. Invalid values fall back to
the default, values < 1 clamp to 1.

Part of NVIDIA#303 (the retry/backoff and partial-pass surfacing are separate). Adds
unit tests for the env resolution.

Signed-off-by: Mark2Mac <Mark2Mac@users.noreply.github.com>
Signed-off-by: keshprad <32313895+keshprad@users.noreply.github.com>
Co-authored-by: Mark2Mac <Mark2Mac@users.noreply.github.com>
Co-authored-by: keshprad <32313895+keshprad@users.noreply.github.com>
Signed-off-by: keshavp <keshavp@nvidia.com>
Closes NVIDIA#21. Closes NVIDIA#131.

The per-file analysis cap (MAX_FILE_BYTES, 1 MB) sits downstream of
InputHandler.resolve(), which pulls scan targets from URLs, zips, or
git clones with no size budget of its own.  As a result, the per-file
cap can be defeated upstream: a multi-GB URL is a memory DoS, a zip
bomb fills the disk before extraction is gated, and a large clone
lands on disk regardless of the per-file analysis budget.  PR NVIDIA#19
explicitly deferred this; this PR addresses the deferred work.

Adds two ingest budgets enforced at every remote/archive ingest path:

- INGEST_MAX_BYTES (100 MiB): caps streamed URL downloads, total
  uncompressed size of zip archives, and post-clone disk usage of
  Git repos.
- INGEST_MAX_ZIP_MEMBERS (10,000): caps the number of entries in a
  single zip (defends against the "many tiny files" zip-bomb variant).

Per ingest path:

- _download_file streams the body in 64 KiB chunks directly to a temp
  file inside the existing session temp dir, with a running byte
  counter.  The cap check fires before each chunk is written, so the
  response body is never accumulated in memory.  Content-Length is
  checked up-front when the server provides it (aborts before reading
  any body bytes); the streamed counter is authoritative if the
  header is missing or malformed.  A breach mid-stream removes the
  partial file before the exception propagates, so an attacker who
  ships exactly INGEST_MAX_BYTES + 1 bytes cannot fill the temp dir.
- _extract_zip sums ZipInfo.file_size across all members and checks
  the member count *before* calling extractall, so classic zip bombs
  (small archive, huge declared uncompressed size) are rejected
  without materialising any of the bomb on disk.
- _clone_git measures the cloned tree's on-disk size after the
  existing 60s timeout completes and rejects + cleans up if it
  exceeds the cap.  Symlinks are skipped to avoid runaway counts on
  malicious /dev/zero-style links.

All limit breaches raise IngestLimitExceededError (subclass of
ValueError so existing callers that catch ValueError keep working)
with a clear message including the limit and the observed size.

Adds 10 unit tests covering: under-cap success paths for URL/zip/clone;
oversized Content-Length rejected before body read; chunked overflow
caught by the streamed counter; *the partial download file is removed
when a breach fires mid-stream*; *streaming to disk produces a file of
the expected size with no intermediate in-memory concatenation*;
declared-uncompressed-oversize zip rejected without extraction;
member-count zip-bomb rejected; oversize clone rejected and cleaned
up.  Full suite: 730 passed, 12 skipped.

README documents both caps and their relationship to the per-file
analysis cap.

Signed-off-by: Rohan Isawe <rohan.isawe@smartsheet.com>
Co-authored-by: Rohan Isawe <rohan.isawe@smartsheet.com>
…cans (NVIDIA#103) (NVIDIA#232)

* fix(analyzer): reduce instructional-prose false positives in static scans (NVIDIA#103)

Signed-off-by: Rod Boev <rod.boev@gmail.com>

* fix(analyzer): preserve direct warning-suppression detection (NVIDIA#103)

Signed-off-by: Rod Boev <rod.boev@gmail.com>

* fix(analyzer): honor quoted and declared benign roles (NVIDIA#103)

Signed-off-by: Rod Boev <rod.boev@gmail.com>

* fix(analyzer): keep adjacent live anti-refusal directives detectable (NVIDIA#103)

Signed-off-by: Rod Boev <rod.boev@gmail.com>

* fix(analyzer): scope benign anti-refusal continuations precisely (NVIDIA#103)

Signed-off-by: Rod Boev <rod.boev@gmail.com>

* fix(analyzer): distinguish declaration headers from live directives (NVIDIA#103)

Signed-off-by: Rod Boev <rod.boev@gmail.com>

* fix(analyzer): treat documentation labels as prose, not examples (NVIDIA#103)

Signed-off-by: Rod Boev <rod.boev@gmail.com>

* test(analyzer): cover declaration and fixture prose edges (NVIDIA#103)

Signed-off-by: Rod Boev <rod.boev@gmail.com>

* fix(analyzer): keep live directives from slipping past prose guards (NVIDIA#103)

Signed-off-by: Rod Boev <rod.boev@gmail.com>

* fix(analyzer): keep ambiguous labels from suppressing live directives (NVIDIA#103)

Signed-off-by: Rod Boev <rod.boev@gmail.com>

* fix(analyzer): preserve live directives through the static runner (NVIDIA#103)

Signed-off-by: Rod Boev <rod.boev@gmail.com>

* fix(analyzer): keep block labels and schema prose from masking live directives (NVIDIA#103)

Signed-off-by: Rod Boev <rod.boev@gmail.com>

* fix(analyzer): preserve the remaining AR2 response-suppression directives (NVIDIA#103)

Signed-off-by: Rod Boev <rod.boev@gmail.com>

* fix(analyzer): preserve multiline documentation directives (NVIDIA#103)

Signed-off-by: Rod Boev <rod.boev@gmail.com>

* fix(analyzer): scope anti-refusal suppression to local clauses (NVIDIA#103)

Signed-off-by: Rod Boev <rod.boev@gmail.com>

* fix(analyzer): tighten anti-refusal benign framing (NVIDIA#103)

Signed-off-by: Rod Boev <rod.boev@gmail.com>

* fix(analyzer): tighten anti-refusal suppression framing (NVIDIA#103)

Signed-off-by: Rod Boev <rod.boev@gmail.com>

---------

Signed-off-by: Rod Boev <rod.boev@gmail.com>
…#238)

* Pin actions/checkout usage to avoid dependency confusion attack

Signed-off-by: Steven Moy <github@stevenmoy.com>

* Pin Docker file dependency to avoid dependency confusion

Signed-off-by: Steven Moy <github@stevenmoy.com>

---------

Signed-off-by: Steven Moy <github@stevenmoy.com>
Signed-off-by: Rod Boev <rod.boev@gmail.com>
Signed-off-by: Rod Boev <rod.boev@gmail.com>
Signed-off-by: Aamir Akram <aamirakram252003@gmail.com>
Signed-off-by: Rod Boev <rod.boev@gmail.com>
* fix: exclude valid OMS signatures from content analysis

OMS bundles necessarily contain long base64-encoded payload, signature, and certificate fields. Generic obfuscated-code checks can misclassify these fields as hidden executable content.

Recognize root-level skill.oms.sig files with the minimal OMS v0.3 DSSE/in-toto structure and inventory them as oms_signature components without sending their contents through static or LLM analyzers.

Preserve normal scanning for malformed, nested, oversized, or unrecognized signature files. Add a pinned real-world fixture, regression coverage, completeness accounting, and documentation.

Recognition is structural only and does not verify signatures, certificates, transparency logs, or signer identity.

Signed-off-by: major-security <73253177+major-security@users.noreply.github.com>

* fix: accept versioned OMS signature predicates

Signed-off-by: Daniel Major <dmajor@nvidia.com>

* test: cover OMS exclusions across report formats

Signed-off-by: Daniel Major <dmajor@nvidia.com>

* fix: preserve semantic ledger evidence for missing files

Signed-off-by: Daniel Major <dmajor@nvidia.com>

---------

Signed-off-by: major-security <73253177+major-security@users.noreply.github.com>
Signed-off-by: Daniel Major <dmajor@nvidia.com>
…IA#338)

This reverts commit 5b07626.

Signed-off-by: Keshav Pradeep <32313895+keshprad@users.noreply.github.com>
…al (NVIDIA#321)

* fix(static): markdown table and quote syntax is not an execution signal

`_is_documentation_context` refuses to treat a line as prose when
`_EXECUTION_SIGNAL` matches it, and that pattern includes `[|>]`. In markdown
those two characters are structure, not shell metacharacters: `|` delimits table
cells and `>` starts a block quote. A governed rule that lands on a table row or
a quoted paragraph is therefore never classified as prose, whatever it says.

The delimiters are now removed before the execution test, and only the
delimiters. Inside a table cell a literal pipe has to be written `\|`
(CommonMark), so a documented `cmd \| tee log` keeps its pipe and still counts
as an execution signal — which is what makes this safe rather than a widening.

Scope, stated plainly: this is a correctness fix, not a precision win. On a
corpus of 65 real skill/plugin units (4415 findings, all triaged by hand) only 8
findings of the governed rules were blocked by markdown structure alone, across
4 files. After the change 1 remains, and it has a genuine execution signal on
the line. The reason to fix it is that the classification is simply wrong, not
that it is frequent.

Nothing is added to `_SEMANTIC_STRING_DOC_PRONE_RULES`: the set stays
{RA1, TM1, AR2}, and the reasoning that excludes PE3 is untouched.

Tests: table row and block quote are classified as prose; a literal escaped pipe
in a cell and a real redirection in a quote still are not; plus a unit test that
`_strip_markdown_structure` touches delimiters and nothing else. Full suite:
1565 passed, 14 skipped, 6 xfailed.

Signed-off-by: Mark2Mac <Mark2Mac@users.noreply.github.com>

* fix(static): make the docstring raw so 3.12 stops warning

The docstring quotes CommonMark's escaped bar, and \| is not a valid
escape sequence: Python 3.12 emits SyntaxWarning on import, which makes
a supported-Python test run noisy. The prefix is the whole fix.

Guarded package-wide rather than per-file: the new test compiles every
shipped module and fails on any SyntaxWarning, so the next one is caught
where it is written instead of in a reviewer's terminal.

Tests: tests/unit 727 passed, 12 skipped.
Signed-off-by: Mark2Mac <Mark2Mac@users.noreply.github.com>

---------

Signed-off-by: Mark2Mac <Mark2Mac@users.noreply.github.com>
Co-authored-by: Mark2Mac <Mark2Mac@users.noreply.github.com>
…atch (NVIDIA#317)

On Windows, run_agent_cli's TemporaryDirectory raised WinError 32 at
__exit__ whenever any live process still held the per-invocation temp
cwd — and the agent CLI's process tree routinely does: the binary is a
.cmd shim whose node children can outlive the direct child, and a
killed-but-unreaped child on the timeout path holds its cwd too. A
directory that is any live process's working directory (or contains any
open handle) cannot be removed on Windows, so cleanup raised *after* the
model had already answered, and llm_analyzer_base recorded the batch as
llm_batch_failed. Under batch concurrency the teardown window widens,
which is why multi-file scans failed while single-file scans passed
(NVIDIA#315). Each invocation already has a unique mkdtemp directory, so this
was never a cross-worker path collision; it is delete-at-exit racing the
process tree's teardown.

Fix: mkdtemp + explicit best-effort cleanup. _cleanup_temp_dir retries
briefly (10 x 0.2s) to reclaim the directory once the holder exits, then
leaks it with a warning instead of raising. A cleanup failure never
outranks a successful response.

Verified on Windows 10 with a mechanism-faithful fake CLI (a .cmd shim
that detaches a grandchild holding the temp cwd): 8 concurrent batches
fail 8/8 with the exact WinError 32 signature from NVIDIA#315 on main, and
pass 8/8 with this change. Controlled experiments confirm both hold
modes (another process's cwd; an open file handle inside the dir) block
rmtree with WinError 32.

Tests: 5 new cases including a Windows-only real-handle hold and a
regression test asserting run_agent_cli returns the response when rmtree
keeps failing. tests/unit/test_agent_cli.py 87/87 on Windows.

Closes NVIDIA#315

Signed-off-by: ppcvote <risky9763@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…fy (NVIDIA#318) (NVIDIA#319)

* fix(supply-chain): treat only == / <= as version pins in requirements.txt

`_extract_packages_from_requirements` kept the captured version for any operator,
so a floor like `pillow>=10.0.0` was recorded as the exact release `10.0.0` and
the OSV/CVE lookup attributed that version's vulnerabilities to an unpinned
dependency — a false CRITICAL on a requirements file that pins nothing.

Only `==` and `<=` bound the dependency to a concrete, CVE-checkable release;
`>=`, `>`, `!=`, `~=` are floors/ranges. This mirrors the guard already present
in `_extract_packages_from_setup_py` (`m.group(2) in ("==", "<=")`), so the two
extractors now agree.

Added a regression test asserting `>=`, `~=`, `!=` and bare names yield
version=None while `==` / `<=` keep the version.

Fixes NVIDIA#294

Signed-off-by: Mark2Mac <Mark2Mac@users.noreply.github.com>

* fix(supply-chain): only exact pins resolve to a version, ranges do not

Addresses the review on NVIDIA#302: the previous guard still admitted non-exact
constraints. `<=8.1.0` matches every earlier release and `==1.*` is a wildcard,
so both were handed to the vulnerability lookup as a version the dependency may
never install.

A vulnerability lookup answers "is THIS release affected?", which is only
meaningful when the manifest admits exactly one release. That predicate is now
explicit and shared instead of being re-derived at each call site:

- `_pinned_version` (PEP 440): only `==` with a fully concrete version. Floors,
  caps, exclusions, compatible releases and wildcard equality yield None.
- `_pinned_npm_version` (semver): only a bare `x.y.z`. npm defaults to caret
  ranges, so `"^1.8.3"` was being stripped into the concrete release `1.8.3`.

Applied to all three extractors — requirements.txt, pyproject.toml and
package.json — because the objection in the review holds verbatim for the two
that were not touched by the original patch.

Note for the maintainer: dropping these specifiers moves more dependencies to
version=None, which NVIDIA#318 shows is currently reported as CRITICAL carrying the
package's worst-ever advisory. The two fixes are complementary; happy to send
the severity side as a separate PR.

Regressions cover both cases named in the review (`<=` and `==1.*`) plus the
npm caret/tilde/wildcard/range forms.

Signed-off-by: Mark2Mac <Mark2Mac@users.noreply.github.com>

* fix(supply-chain): SC4 must not claim a vulnerability it did not verify

When a manifest admits a range, no version is resolved and OSV is queried by name
alone. The advisories that come back are the package's history, not a match against
the release that will be installed: the worst of them may predate every version the
range admits. Using that as the finding's severity turns 'setuptools>=61' into a
CRITICAL 'Known Vulnerable Dependency'.

Scanning 65 skill/plugin units, every SC4 finding in the corpus came from this or
from a range being read as a pin (NVIDIA#294/NVIDIA#302). Not one manifest pinned a vulnerable
release.

The lack of pinning is already reported by SC1, so what is left for SC4 to say is
'could not verify', and it must not outrank a real version match: severity capped at
LOW, confidence 0.4, and wording that states the limit instead of implying a match.
Version-matched findings are unchanged.

Closes NVIDIA#318

Signed-off-by: Mark2Mac <Mark2Mac@users.noreply.github.com>

* fix(supply-chain): preserve full PEP 440 pins

Signed-off-by: keshavp <32313895+keshprad@users.noreply.github.com>

* fix(supply-chain): parse pip requirement options

Signed-off-by: keshavp <32313895+keshprad@users.noreply.github.com>

* style: format supply chain tests

Signed-off-by: keshavp <32313895+keshprad@users.noreply.github.com>

---------

Signed-off-by: Mark2Mac <Mark2Mac@users.noreply.github.com>
Signed-off-by: keshavp <32313895+keshprad@users.noreply.github.com>
Co-authored-by: Mark2Mac <Mark2Mac@users.noreply.github.com>
Co-authored-by: keshavp <32313895+keshprad@users.noreply.github.com>
`_is_layout_only_span()` is what keeps MP2 from reporting alignment as context
stuffing, and it has no test. The only layout case covered today is `"=" * 80`,
which never reaches the helper — the older single-character guard skips it first.

Measured on a corpus of 4406 files from 65 skill/plugin units, comparing the
analyzer before the helper landed with `main` today:

  MP2 findings   279 -> 4

The four survivors come from MP2's prose patterns ("exceed context window"), not
from the repetition pattern. So the helper is carrying 275 of 279 findings, with
nothing pinning its behaviour.

The three cases added here are each reported when the helper is stubbed out, which
is what makes them regressions rather than restatements of the guard above them:
a dash rule, a run of padded columns, and a box edge with padding. The fourth test
locks the other side, `_MAX_LAYOUT_ONLY_SPAN`: past that width layout stops being a
plausible explanation and the run is reported again.

No source change.

Signed-off-by: Mark2Mac <Mark2Mac@users.noreply.github.com>
Co-authored-by: Mark2Mac <Mark2Mac@users.noreply.github.com>
The README has no mention of the Verified Skills pipeline, the skills
catalog, or the hosted documentation, so a reader arriving at this repo
has no path to any of them.

- Overview: one line placing SkillSpector in the pipeline, linking the
  docs home and the skills catalog.
- Documentation: lead with the hosted scanning guide, ahead of the
  repo-local developer docs, since that is what most readers want.

Signed-off-by: Moshe Abramovitch <moshea@nvidia.com>
Signed-off-by: Keshav Prasad <32313895+keshprad@users.noreply.github.com>
* fix(output-handling): ignore RegExp exec parsing

Signed-off-by: Christopher Kevin <christopherk@nvidia.com>

* fix(output-handling): guard RegExp exec mutations

Signed-off-by: Christopher Kevin <christopherk@nvidia.com>

* fix(output-handling): avoid unverified mutation inference

Signed-off-by: Christopher Kevin <christopherk@nvidia.com>

* test(output-handling): cover regexp class syntax

Signed-off-by: Christopher Kevin <christopherk@nvidia.com>

* fix(output-handling): fail closed across line comments

Signed-off-by: Christopher Kevin <christopherk@nvidia.com>

* fix(output-handling): handle legacy HTML comments

Signed-off-by: Christopher Kevin <christopherk@nvidia.com>

---------

Signed-off-by: Christopher Kevin <christopherk@nvidia.com>
mohgupta-ship-it and others added 4 commits August 4, 2026 18:06
…on (NVIDIA#332)

* fix(analyzer): detect full environment reads via AST

Signed-off-by: Mohit Gupta <mohgupta@nvidia.com>

* refactor(analyzers): share Python AST parsing

Signed-off-by: Mohit Gupta <mohgupta@nvidia.com>

* fix(analyzer): detect keyword environment secret reads

Signed-off-by: keshavp <32313895+keshprad@users.noreply.github.com>

---------

Signed-off-by: Mohit Gupta <mohgupta@nvidia.com>
Signed-off-by: keshavp <32313895+keshprad@users.noreply.github.com>
Co-authored-by: keshavp <32313895+keshprad@users.noreply.github.com>
Signed-off-by: keshprad <32313895+keshprad@users.noreply.github.com>
Sanitized from internal release 589281bf188f28eb59d7db8fe10858612b5f3d1f after the repository OSS scrubber and full unit-test gate.
@will-exaforce
will-exaforce requested a review from smoy August 10, 2026 19:52
@will-exaforce
will-exaforce merged commit 30dcadd into main Aug 11, 2026
2 of 5 checks passed
@will-exaforce
will-exaforce deleted the wbeasley/sync-upstream-2.8.1 branch August 11, 2026 09:45
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.