Skip to content

chore(ingestion): drop pylint, expand ruff#27774

Open
IceS2 wants to merge 5 commits intomainfrom
pylint-to-ruff-stage2a
Open

chore(ingestion): drop pylint, expand ruff#27774
IceS2 wants to merge 5 commits intomainfrom
pylint-to-ruff-stage2a

Conversation

@IceS2
Copy link
Copy Markdown
Contributor

@IceS2 IceS2 commented Apr 27, 2026

Summary

Replace pylint with a coherent ruff-only stack. Pylint is dropped from dev deps and CI workflows; ruff's selected ruleset expands to ~22 families covering style, bug catchers, hygiene, and the pylint port (PLE/PLC/PLW/PLR, with the noisy "too-many-X" complexity caps and magic-value comparison disabled).

This is Stage 2c of the modernize roadmap: tooling-only swap, all existing violations grandfathered via # noqa: <CODE> (~12,130 markers across ~1,400 files). Cleanup is deferred to follow-up PRs that drop noqas one rule at a time.

What's selected

Each rule family is annotated inline in ingestion/pyproject.toml with a one-line rationale. Highlights:

  • Style + correctness: E, W, F, I, N, UP
  • Bug catchers: B, C4, C90, RET, SIM, TRY
  • Hygiene: PIE, ICN, T20, TC, TID, PTH, PERF
  • Pylint port: PLE, PLC, PLW, PLR (with PLR0904/0911-0917, PLR2004, PLR1711, PLR2044 ignored — noisy on connector dispatchers)
  • Ruff-native: RUF (incl. RUF100 unused-noqa, so debt bleeds out as code is touched)

What's removed

  • .pylintrc (root) — duplicate of the ingestion pylint config
  • [tool.pylint.*] block in ingestion/pyproject.toml (~140 lines)
  • ingestion/plugins/{print_checker,import_checker}.py + tests + README — replaced by built-in T20 and TID251 banned-api respectively
  • pylint dep from ingestion/setup.py and openmetadata-airflow-apis/pyproject.toml
  • make lint Makefile target + the pylint invocation in make py_format_check
  • Dead pylint TODO comment + ignored test entry in ingestion/noxfile.py

Cwd-stable config

ruff is invoked from two locations:

  • repo root (pre-commit hooks, CI)
  • ingestion/ (make py_format_check)

The src, extend-exclude, and per-file-ignores entries are listed twice — once relative to ingestion/ and once with the ingestion/ prefix — so first-party isort detection and exclusions match in both invocations.

Documentation sweep

Replaced make lint references in CLAUDE.md, AGENTS.md, DEVELOPER.md, .github/copilot-instructions.md, and 6 SKILL files with the apply+verify shape make py_format && make py_format_check. Note: make py_format is not a strict superset of make lint — it only applies auto-fixable violations; make py_format_check catches the rest.

Basedpyright baseline regen

ruff format reflowed multi-line signatures in ~70 files, shifting type-error column positions. basedpyright's baseline matches by (file path, error code, column range), so column shifts caused 19 entries to mis-align. Net baseline diff is small — 154 lines in/out of the 13MB baseline.json, purely positional.

Verified locally

  • make py_format_check → All checks passed
  • nox --no-venv -s static-checks → 0 errors, 0 warnings, 0 notes (basedpyright)

Replace pylint with a coherent ruff-only stack (Stage 2c of the modernize
roadmap). Pylint is dropped from dev deps and CI workflows; ruff selected
ruleset expanded to ~22 families covering style, bug catchers, hygiene,
and the pylint port (PLE/PLC/PLW/PLR with the noisy "too-many-X"
complexity caps + magic-value disabled).

What's selected (with rationale in pyproject.toml):
  E, W, F, I, N         — style + correctness baseline + naming
  UP                    — pyupgrade (py>=3.10 modernizations)
  B, C4, C90, RET, SIM, TRY  — bug catchers
  PIE, ICN, T20, TC, TID, PTH, PERF  — hygiene
  PLE, PLC, PLW, PLR    — pylint port (PLR complexity caps ignored)
  RUF                   — ruff-native (incl. RUF100 unused-noqa)

What's removed:
  - .pylintrc (root) — duplicate of the ingestion pylint config
  - [tool.pylint.*] block in ingestion/pyproject.toml (~140 lines)
  - ingestion/plugins/{print_checker,import_checker}.py + tests + README
    (replaced by built-in T20 + TID251 banned-api respectively)
  - pylint dep from ingestion/setup.py and openmetadata-airflow-apis/pyproject.toml
  - `make lint` Makefile target + the pylint invocation in py_format_check
  - dead pylint TODO comment + ignored test entry in noxfile.py

Cwd-stable config: ruff is invoked both from the repo root (pre-commit,
CI) and from ingestion/ (`make py_format_check`). The `src`,
`extend-exclude`, and per-file-ignores entries are listed twice — once
relative to ingestion/ and once with the `ingestion/` prefix — so
first-party isort detection and exclusions match in both invocations.

Grandfathering: ran `ruff check --add-noqa` once + format-stable
iteration. ~12,130 noqa directives across ~1,400 files. Cleanup is
deferred to follow-up PRs that drop noqas one rule at a time.

Documentation sweep: replaced `make lint` references in CLAUDE.md,
AGENTS.md, DEVELOPER.md, copilot-instructions, and 6 SKILL files with
the apply+verify shape `make py_format && make py_format_check`.
`make py_format` is NOT a strict superset of pylint — it only applies
auto-fixable violations; `make py_format_check` catches the rest.

Basedpyright baseline regenerated: ruff format reflowed multi-line
signatures in ~70 files, shifting type-error column positions. The
basedpyright baseline matches by (file path, error code, range), so
column shifts caused 19 entries to mis-align. Net diff is small
(154 lines in/out of the 13MB baseline.json) — purely positional.

Verified locally:
  - make py_format_check         → All checks passed
  - nox --no-venv -s static-checks → 0 errors, 0 warnings, 0 notes
Copilot AI review requested due to automatic review settings April 27, 2026 18:19
@IceS2 IceS2 requested review from a team, akash-jain-10, harshach and tutte as code owners April 27, 2026 18:19
@github-actions github-actions Bot added Ingestion safe to test Add this label to run secure Github workflows on PRs labels Apr 27, 2026
Comment thread ingestion/pyproject.toml
Comment thread ingestion/src/metadata/ingestion/models/custom_pydantic.py Fixed
Comment thread ingestion/src/metadata/utils/secrets/azure_kv_secrets_manager.py Fixed
Comment thread ingestion/src/metadata/utils/secrets/kubernetes_secrets_manager.py Fixed
Comment thread ingestion/src/metadata/utils/secrets/kubernetes_secrets_manager.py Fixed
Three remaining stale-tooling references after Stage 2c:

  - `ingestion/noxfile.py` `lint` session was still calling `black --check`,
    `isort --check-only`, `pycln --diff`. Those tools aren't installed
    anywhere (we dropped them from dev deps). Replace with the ruff
    equivalents that mirror `make py_format_check`.
  - `skills/standards/code_style.md`: stack listed as `black + isort +
    pycln`; line length claimed 88 (black default). Both wrong: stack is
    ruff, line length is 120.
  - `skills/connector-building/SKILL.md`: `make py_format` comment said
    `# black + isort + pycln`. Same swap.
Resolved conflicts in two files where main's PR #27728 swapped
`logger.warning` → `logger.error` while Stage 2c added noqa markers:

  - ingestion/src/metadata/clients/domo_client.py:168 (TRY201 noqa preserved)
  - ingestion/src/metadata/ingestion/source/pipeline/spline/utils.py
    two log blocks (TRY400 noqa preserved on traceback.format_exc lines)

Re-ran `ruff check --add-noqa` on the merged files; main's auto-merged
warning→error swap introduced 94 new TRY400 violations across data-quality
validators which are now grandfathered in line.

Regenerated basedpyright baseline in a Linux Docker container so column
positions match CI (Ubuntu) exactly. Local macOS arm64 will see column-
shifted entries due to stub differences between macOS and Linux wheels;
`--baselinemode=discard` mode tolerates the drift downstream. Pinned
`pythonPlatform = "Linux"` in pyproject.toml so CI analysis is platform-
stable across runners.
Comment thread ingestion/src/metadata/ingestion/source/dashboard/metabase/client.py Outdated
@IceS2
Copy link
Copy Markdown
Contributor Author

IceS2 commented Apr 27, 2026

Code Review ⚠️ Changes requested 0 resolved / 2 findings

Expands Ruff coverage to Stage 2c while dropping Pylint, but the transition leaves dead per-file-ignores for unselected rules and inadvertently changes log severity semantics by promoting warnings to errors.
⚠️ Quality: Bulk warning→error promotion changes log severity semantics

📄 ingestion/src/metadata/ingestion/source/dashboard/metabase/client.py:130 📄 ingestion/src/metadata/ingestion/source/dashboard/metabase/client.py:165 📄 ingestion/src/metadata/ingestion/source/dashboard/metabase/client.py:179 📄 ingestion/src/metadata/ingestion/source/dashboard/metabase/client.py:242 📄 ingestion/src/metadata/ingestion/source/dashboard/metabase/client.py:257 📄 ingestion/src/metadata/ingestion/source/dashboard/metabase/client.py:272 📄 ingestion/src/metadata/ingestion/source/dashboard/metabase/client.py:287 📄 ingestion/src/metadata/ingestion/source/dashboard/microstrategy/client.py:139 📄 ingestion/src/metadata/ingestion/source/dashboard/microstrategy/client.py:158 📄 ingestion/src/metadata/ingestion/source/dashboard/microstrategy/client.py:176 📄 ingestion/src/metadata/ingestion/source/dashboard/microstrategy/client.py:208 📄 ingestion/src/metadata/ingestion/source/dashboard/microstrategy/client.py:228 📄 ingestion/src/metadata/ingestion/source/dashboard/microstrategy/client.py:244 📄 ingestion/src/metadata/ingestion/source/dashboard/microstrategy/client.py:263 📄 ingestion/src/metadata/ingestion/source/dashboard/qlikcloud/client.py:127 📄 ingestion/src/metadata/ingestion/source/dashboard/qlikcloud/client.py:147 📄 ingestion/src/metadata/ingestion/source/dashboard/qlikcloud/client.py:167 📄 ingestion/src/metadata/ingestion/source/dashboard/qlikcloud/client.py:197 📄 ingestion/src/metadata/ingestion/source/dashboard/qlikcloud/client.py:217 📄 ingestion/src/metadata/ingestion/source/dashboard/qlikcloud/client.py:237 📄 ingestion/src/metadata/ingestion/source/dashboard/qlikcloud/client.py:249 📄 ingestion/src/metadata/ingestion/source/dashboard/qliksense/client.py:159 📄 ingestion/src/metadata/ingestion/source/dashboard/qliksense/client.py:175 📄 ingestion/src/metadata/ingestion/source/dashboard/qliksense/client.py:241 📄 ingestion/src/metadata/ingestion/source/dashboard/qliksense/client.py:256 📄 ingestion/src/metadata/ingestion/source/dashboard/qliksense/client.py:305 📄 ingestion/src/metadata/ingestion/source/pipeline/kafkaconnect/metadata.py:216 📄 ingestion/src/metadata/ingestion/source/pipeline/kafkaconnect/metadata.py:267 📄 ingestion/src/metadata/ingestion/source/pipeline/kafkaconnect/metadata.py:596 📄 ingestion/src/metadata/ingestion/source/pipeline/kafkaconnect/metadata.py:903 📄 ingestion/src/metadata/ingestion/source/pipeline/kafkaconnect/metadata.py:978 📄 ingestion/src/metadata/ingestion/source/pipeline/kafkaconnect/metadata.py:1031 📄 ingestion/src/metadata/ingestion/source/pipeline/kafkaconnect/client.py:238 📄 ingestion/src/metadata/ingestion/source/pipeline/kafkaconnect/client.py:322 📄 ingestion/src/metadata/ingestion/source/pipeline/kafkaconnect/client.py:369 📄 ingestion/src/metadata/ingestion/source/storage/s3/metadata.py:691 📄 ingestion/src/metadata/ingestion/source/messaging/pubsub/metadata.py:207 📄 ingestion/src/metadata/ingestion/source/messaging/pubsub/metadata.py:210 📄 ingestion/src/metadata/ingestion/source/messaging/pubsub/metadata.py:230 📄 ingestion/src/metadata/ingestion/source/messaging/pubsub/metadata.py:348 📄 ingestion/src/metadata/ingestion/source/mcp/client.py:149 📄 ingestion/src/metadata/ingestion/source/mcp/client.py:290 📄 ingestion/src/metadata/ingestion/source/mcp/client.py:472 📄 ingestion/src/metadata/ingestion/source/mcp/client.py:517 📄 ingestion/src/metadata/ingestion/connections/test_connections.py:139

This commit promotes ~60 logger.warning() calls to logger.error() across the codebase. Many of these are in except blocks for expected, recoverable failures — e.g., failing to fetch a single dashboard, failing to parse one schema, failing to get a bucket region — where the code returns None/[] and continues processing. These are classic warning-level events: something went wrong but the system continues operating.

Promoting them to error level has real operational impact:

* Monitoring/alerting systems typically trigger on ERROR log volume, so these will generate false alerts in production.

* It conflates "we couldn't parse one topic schema" (degraded but functional) with actual errors that need investigation.

* The `# noqa: TRY400` suppression on every line suggests ruff's TRY400 rule (which flags `logging.error()` inside `except` — preferring `logging.exception()`) is being silenced rather than followed.

If the goal is to satisfy a linter rule that flagged logger.warning inside except blocks, the idiomatic fix is either:

1. Keep `logger.warning()` and suppress the specific rule that triggered the change, or

2. Use `logger.warning(..., exc_info=True)` to attach the traceback without elevating severity.

Examples of clearly wrong severity: failing to fetch a Metabase collection list, failing to parse a single connector's column mappings, failing to get an S3 bucket region — none of these are errors in the logging sense.
💡 Quality: Dead per-file-ignore: S101 not enforced since S family unselected

🤖 Prompt for agents

Options

Was this helpful? React with 👍 / 👎 | Gitar

The warnings were promoted to error here: #27728

IceS2 added 2 commits April 27, 2026 21:05
Per gitar-bot's review on PR #27774:

1. Main's PR #27728 promoted ~60 `logger.warning()` → `logger.error()`
   inside `except` blocks. Those changes landed on main with their own
   baseline updates. Our PR doesn't promote anything — the merge from
   origin/main brought those `error` calls along with their baseline
   entries.

   The bot interpreted the `# noqa: TRY400` we added next to those lines
   as us silencing the rule case-by-case. Cleaner: globally ignore
   TRY400 in pyproject.toml, with a comment explaining why the codebase's
   `logger.error(...)` + separate `logger.debug(traceback.format_exc())`
   pattern is intentional. Strip ~430 per-line `# noqa: TRY400` markers
   from source.

2. Document that `S101` in `per-file-ignores` is a forward-looking
   entry — flake8-bandit (`S`) is not yet selected, so the rule is
   no-op today; the entry stays so when `S` lands later, tests don't
   immediately error.

Reverts the platform pin and Linux Docker–generated baseline. Keep
main's baseline intact and let CI surface the exact column-shifted
entries; the team will decide whether to fix in-place (revert format
on affected files) or add per-line `# pyright: ignore` markers.
Main's baseline was stale relative to recently-added connectors
(McpConnection, CustomDriveConnection) that lack common attributes
like `hostPort`, `database`, `catalog` etc. — all sites that access
those attributes via the union-typed `serviceConnection.root.config`
fire `reportAttributeAccessIssue` errors that aren't baselined.

71 errors + 58 warnings absorbed. Local macOS regen; pushing to see
CI's drift count. Per the basedpyright-baseline-and-ci PR experience,
macOS↔Linux column drift on this size of regen has historically been
1-7 residuals.
@gitar-bot
Copy link
Copy Markdown

gitar-bot Bot commented Apr 27, 2026

Code Review ✅ Approved 2 resolved / 2 findings

Replaces pylint with ruff for static analysis, expanding coverage to Stage 2c. Addresses redundant S101 ignore rules and corrects log severity semantics from the bulk warning-to-error promotion.

✅ 2 resolved
Quality: Dead per-file-ignore: S101 not enforced since S family unselected

📄 ingestion/pyproject.toml:214-215
The per-file-ignores for test files include S101 (assert) and PLC0415 (import-outside-toplevel), but the S (flake8-bandit) family is not in select. This means S101 is a no-op today. Similarly, COM812 and ISC001 in the global ignore list reference unselected families.

This is likely intentional future-proofing for when S is added in a later stage, but it's worth a comment so maintainers don't puzzle over it.

Quality: Bulk warning→error promotion changes log severity semantics

📄 ingestion/src/metadata/ingestion/source/dashboard/metabase/client.py:130 📄 ingestion/src/metadata/ingestion/source/dashboard/metabase/client.py:165 📄 ingestion/src/metadata/ingestion/source/dashboard/metabase/client.py:179 📄 ingestion/src/metadata/ingestion/source/dashboard/metabase/client.py:242 📄 ingestion/src/metadata/ingestion/source/dashboard/metabase/client.py:257 📄 ingestion/src/metadata/ingestion/source/dashboard/metabase/client.py:272 📄 ingestion/src/metadata/ingestion/source/dashboard/metabase/client.py:287 📄 ingestion/src/metadata/ingestion/source/dashboard/microstrategy/client.py:139 📄 ingestion/src/metadata/ingestion/source/dashboard/microstrategy/client.py:158 📄 ingestion/src/metadata/ingestion/source/dashboard/microstrategy/client.py:176 📄 ingestion/src/metadata/ingestion/source/dashboard/microstrategy/client.py:208 📄 ingestion/src/metadata/ingestion/source/dashboard/microstrategy/client.py:228 📄 ingestion/src/metadata/ingestion/source/dashboard/microstrategy/client.py:244 📄 ingestion/src/metadata/ingestion/source/dashboard/microstrategy/client.py:263 📄 ingestion/src/metadata/ingestion/source/dashboard/qlikcloud/client.py:127 📄 ingestion/src/metadata/ingestion/source/dashboard/qlikcloud/client.py:147 📄 ingestion/src/metadata/ingestion/source/dashboard/qlikcloud/client.py:167 📄 ingestion/src/metadata/ingestion/source/dashboard/qlikcloud/client.py:197 📄 ingestion/src/metadata/ingestion/source/dashboard/qlikcloud/client.py:217 📄 ingestion/src/metadata/ingestion/source/dashboard/qlikcloud/client.py:237 📄 ingestion/src/metadata/ingestion/source/dashboard/qlikcloud/client.py:249 📄 ingestion/src/metadata/ingestion/source/dashboard/qliksense/client.py:159 📄 ingestion/src/metadata/ingestion/source/dashboard/qliksense/client.py:175 📄 ingestion/src/metadata/ingestion/source/dashboard/qliksense/client.py:241 📄 ingestion/src/metadata/ingestion/source/dashboard/qliksense/client.py:256 📄 ingestion/src/metadata/ingestion/source/dashboard/qliksense/client.py:305 📄 ingestion/src/metadata/ingestion/source/pipeline/kafkaconnect/metadata.py:216 📄 ingestion/src/metadata/ingestion/source/pipeline/kafkaconnect/metadata.py:267 📄 ingestion/src/metadata/ingestion/source/pipeline/kafkaconnect/metadata.py:596 📄 ingestion/src/metadata/ingestion/source/pipeline/kafkaconnect/metadata.py:903 📄 ingestion/src/metadata/ingestion/source/pipeline/kafkaconnect/metadata.py:978 📄 ingestion/src/metadata/ingestion/source/pipeline/kafkaconnect/metadata.py:1031 📄 ingestion/src/metadata/ingestion/source/pipeline/kafkaconnect/client.py:238 📄 ingestion/src/metadata/ingestion/source/pipeline/kafkaconnect/client.py:322 📄 ingestion/src/metadata/ingestion/source/pipeline/kafkaconnect/client.py:369 📄 ingestion/src/metadata/ingestion/source/storage/s3/metadata.py:691 📄 ingestion/src/metadata/ingestion/source/messaging/pubsub/metadata.py:207 📄 ingestion/src/metadata/ingestion/source/messaging/pubsub/metadata.py:210 📄 ingestion/src/metadata/ingestion/source/messaging/pubsub/metadata.py:230 📄 ingestion/src/metadata/ingestion/source/messaging/pubsub/metadata.py:348 📄 ingestion/src/metadata/ingestion/source/mcp/client.py:149 📄 ingestion/src/metadata/ingestion/source/mcp/client.py:290 📄 ingestion/src/metadata/ingestion/source/mcp/client.py:472 📄 ingestion/src/metadata/ingestion/source/mcp/client.py:517 📄 ingestion/src/metadata/ingestion/connections/test_connections.py:139
This commit promotes ~60 logger.warning() calls to logger.error() across the codebase. Many of these are in except blocks for expected, recoverable failures — e.g., failing to fetch a single dashboard, failing to parse one schema, failing to get a bucket region — where the code returns None/[] and continues processing. These are classic warning-level events: something went wrong but the system continues operating.

Promoting them to error level has real operational impact:

  • Monitoring/alerting systems typically trigger on ERROR log volume, so these will generate false alerts in production.
  • It conflates "we couldn't parse one topic schema" (degraded but functional) with actual errors that need investigation.
  • The # noqa: TRY400 suppression on every line suggests ruff's TRY400 rule (which flags logging.error() inside except — preferring logging.exception()) is being silenced rather than followed.

If the goal is to satisfy a linter rule that flagged logger.warning inside except blocks, the idiomatic fix is either:

  1. Keep logger.warning() and suppress the specific rule that triggered the change, or
  2. Use logger.warning(..., exc_info=True) to attach the traceback without elevating severity.

Examples of clearly wrong severity: failing to fetch a Metabase collection list, failing to parse a single connector's column mappings, failing to get an S3 bucket region — none of these are errors in the logging sense.

Options

Display: compact → Showing less information.

Comment with these commands to change:

Compact
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

@IceS2 IceS2 changed the title chore(ingestion): drop pylint, expand ruff to Stage 2c chore(ingestion): drop pylint, expand ruff Apr 27, 2026
Comment on lines +5095 to +5096
"startColumn": 35,
"endColumn": 41,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

what are these?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

updating the baseline that tells basedpyright where the issues are. It is generated by basedpyright by runnning:

https://docs.astral.sh/ruff/rules/#flake8-tidy-imports-tid

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

does it skip these existing failures ?

Comment thread ingestion/plugins/import_checker.py
@github-actions
Copy link
Copy Markdown
Contributor

🟡 Playwright Results — all passed (10 flaky)

✅ 3964 passed · ❌ 0 failed · 🟡 10 flaky · ⏭️ 86 skipped

Shard Passed Failed Flaky Skipped
✅ Shard 1 299 0 0 4
🟡 Shard 2 741 0 3 8
🟡 Shard 3 747 0 1 7
🟡 Shard 4 756 0 3 18
✅ Shard 5 687 0 0 41
🟡 Shard 6 734 0 3 8
🟡 10 flaky test(s) (passed on retry)
  • Features/ActivityAPI.spec.ts › Activity event is created when description is updated (shard 2, 1 retry)
  • Features/ActivityAPI.spec.ts › Activity event shows the actor who made the change (shard 2, 1 retry)
  • Features/IncidentManager.spec.ts › Verify filters in Incident Manager's page (shard 2, 1 retry)
  • Features/RTL.spec.ts › Verify Following widget functionality (shard 3, 1 retry)
  • Pages/DataContracts.spec.ts › Create Data Contract and validate for Api Collection (shard 4, 1 retry)
  • Pages/DataProductAndSubdomains.spec.ts › Add assets to data product and verify count (shard 4, 1 retry)
  • Pages/DomainAdvanced.spec.ts › Remove multiple assets from domain at once (shard 4, 1 retry)
  • Pages/Lineage/DataAssetLineage.spec.ts › Column lineage for searchIndex -> container (shard 6, 1 retry)
  • Pages/Lineage/LineageFilters.spec.ts › Verify lineage schema filter selection (shard 6, 1 retry)
  • Pages/Users.spec.ts › Check permissions for Data Steward (shard 6, 1 retry)

📦 Download artifacts

How to debug locally
# Download playwright-test-results-<shard> artifact and unzip
npx playwright show-trace path/to/trace.zip    # view trace

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

Labels

Ingestion safe to test Add this label to run secure Github workflows on PRs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants