[UPSTREAM CHANGES] latest changes as of Mon Jul 13 2026 03:40:08 GMT+0000 (Coordinated Universal Time) - #237
Open
github-actions[bot] wants to merge 5726 commits into
Open
[UPSTREAM CHANGES] latest changes as of
Mon Jul 13 2026 03:40:08 GMT+0000 (Coordinated Universal Time)#237github-actions[bot] wants to merge 5726 commits into
github-actions[bot] wants to merge 5726 commits into
Conversation
The lock file kept the workspace member entry after pyproject.toml lost it; CI failed with "Distribution not found at .../gt_rust". Regenerated with ``uv lock``. Disclosure: this MR was prepared with assistance from Claude. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The ingest hot path was a mix of native async cursors (raw SQL via async-backend) and Django ORM convenience methods (abulk_create, aupdate, aexists, async-iter on a sync QuerySet) that thread-pool through the sync connection. The latter group made the hot path double-counted across two pools and opaque to async-aware test assertions. Convert every remaining sync_to_async-wrapped DB call in process_issue_events / process_transaction_events to either raw async SQL via async_db.fetchall/execute helpers or async-backend's AsyncQuerySet (debug bundles, where ORM model construction with select_related is the right tool). Issue + IssueHash creation now runs inside async_atomic with raw INSERTs; the IntegrityError race-recovery path reads issue_id back via a SELECT. Replace assertNumQueries on the affected hot-path tests with a new AsyncQueryCounter (glitchtip/test_utils/async_query_counter.py) that patches AsyncCursorWrapper at the class level, so it observes queries across whatever task the sync test client dispatches into. Sync queries from setUp fixtures are now ignored by design. test_store_api.setUp gains cache.clear() so TransactionTestCase's RESTART IDENTITY truncation can't race the auth/throttle cache between tests in the class.
Project baseline is Python 3.12+; PEP 604 / PEP 585 are native and PEP 563 deferred evaluation isn't relied on. Use a forward-reference string for the self-referential return annotation instead.
Vulture flagged exc_type/exc_value/traceback as unused. Replace with *_exc_info — the leading underscore tells vulture the variable is intentionally unused. Also expand the module docstring to spell out why AsyncQueryCounter exists and what would have to change before AsyncCaptureQueriesContext from django-async-backend can replace it (every middleware in MIDDLEWARE has to be async_capable, and a recalibration pass to absorb the BEGIN/COMMIT delta in queries_log). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add a prominent docstring banner and an import-time DeprecationWarning to glitchtip/wsgi.py. GlitchTip is async-first now: WSGI bridges every async view (including the entire ingest hot path) through async_to_sync, leaks a task-local async-backend DB connection per request, and bypasses the glitchtip.ingest_asgi fast-path dispatcher entirely. Custom deployments that bypass our scripts and import glitchtip.wsgi directly will now see the warning in their logs and have a clear pointer to glitchtip.asgi:application under Granian. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
feat(ingest): native async DB cursors via django-async-backend See merge request glitchtip/glitchtip-backend!2343
The release Docker build previously ran on a single amd64 runner and
emulated arm64 layers via QEMU (`multiarch/qemu-user-static`). Emulated
ARM builds are 5-10x slower than native, dominating the wall time of
every release tag.
GitLab now exposes native aarch64 SaaS runners (tag
`saas-linux-medium-arm64`), so split the multi-arch build into two
parallel native jobs (one per arch) that push arch-suffixed tags, plus a
small `release_manifest_merge` job that uses
`docker buildx imagetools create` to publish the user-facing tags
(`latest`, `X.Y.Z`, `X.Y`, `X`) as a manifest list referencing both
arches. A `release_prepare_assets` job downloads the frontend
`dist/` artifact once and shares it with both arch builds.
Also add a `validate_build_{amd64,arm64}` matrix that runs on MRs into
the default branch and on master pushes. It builds the production
Dockerfile natively for each arch (no push) and runs a cheap
`python -c "import django, glitchtip"` smoke test against the resulting
image. This catches "build succeeds but image won't run on this arch"
regressions before they reach a release tag, where the QEMU build was
the first time we discovered them.
Triggers for the release jobs are unchanged: protected vX.Y.Z tags
only, never on schedules or MRs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drop verbose intros and the QEMU/legacy-job references; keep only the non-obvious "why" (smoke test purpose, no-DinD note for imagetools). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ci: native multi-arch builds + per-MR build validation See merge request glitchtip/glitchtip-backend!2349
Bulk INSERT/UPDATE in event ingest, log ingest, and the transaction pipeline previously expanded one ``(%s, %s, ...)`` tuple per row into a ``VALUES`` list via ``execute_mogrified_values``. That has three costs that grow with batch size: a per-row mogrify round-trip in Python, a distinct statement shape per batch length (poor plan-cache reuse), and the 65535 bind-parameter ceiling Postgres enforces, which clamps batches on wide schemas (the 13-column ``issue_events_issueevent`` insert tops out around 5000 rows). Pass per-column arrays to ``unnest(%s::T[], %s::T[], ...)`` instead. One parameter per column regardless of batch size; one statement shape; no bind-param ceiling. Synthetic benchmark (Postgres 18, unlogged tables, 30 rounds, local docker): issue_events_issueevent 13 cols 500 rows: 26% faster (45ms -> 33ms median) issue_events_issuetag 6 cols 2000 rows: 52% faster (70ms -> 34ms median) Converted call sites: - apps/event_ingest/process_event.py: issueevent INSERT, issuetag INSERT, update_issues UPDATE, update_statistics, update_org_statistics, transactiongroup INSERT, spanstaging INSERT, _update_transaction_group_stats Phase 2, and the two IN(VALUES) lookups (_fetch_issue_hashes_raw, _fetch_transaction_groups), now rewritten as JOIN unnest(). - apps/logs/process_logs.py: logevent INSERT, log_statistics upsert, resource_lookup upsert. Left as VALUES (not worth converting): the small release/environment fanout (tiny batches), and ``_update_transaction_group_stats`` Phase 1 which carries an int[] histogram per row that doesn't trivially unnest. Adds two thin helpers in apps/shared/async_db.py — ``execute_unnest`` and ``fetchall_unnest`` — that transpose row-major data to per-column lists before executing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
perf(ingest): switch hot-path bulk writes from VALUES to UNNEST See merge request glitchtip/glitchtip-backend!2350
Same conversion as the prior commit on this branch but for the uptime hot path: the upsert previously mogrified one ``(%s,%s,%s)`` tuple per organization in a Python loop, joined them into a ``VALUES`` list, and ran the whole thing through ``sync_to_async``. Switch to async-native ``execute_unnest`` with ``async_atomic`` so we drop both the per-row Python work and the sync_to_async thread hop on every check flush (every 1 s or 100 checks, whichever comes first). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
perf(uptime): switch update_uptime_statistics from VALUES to UNNEST See merge request glitchtip/glitchtip-backend!2351
The Stripe webhook handler was hitting unrecoverable failures on transient 429 lock_timeout responses from the Stripe API, which Stripe itself flags as retryable via the Stripe-Should-Retry header. Two related issues: 1. stripe_get/stripe_post had no retry logic — any non-200 response immediately raised. Now they share a retry helper that backs off exponentially on 429/5xx, honoring the Stripe-Should-Retry header when present. 2. The webhook view marked event IDs as processed via cache.aadd before actually processing them. If processing raised, Stripe's webhook retry would arrive, hit the dedup cache, and be silently dropped. The mark is now released on failure so retries can reprocess. 🤖 Generated with Claude Code
fix(stripe): retry transient API errors and release dedup key on failure See merge request glitchtip/glitchtip-backend!2353
`unix_to_datetime` used `datetime.fromtimestamp(ts)` (no `tz` arg), which returns a naive datetime in the process's local time, then attached `settings.TIME_ZONE` via `make_aware`. This only produced correct UTC values because the standard container TZ is UTC — naive UTC tagged as UTC happens to round-trip correctly. If a deployment sets `TZ` to a non-UTC zone (e.g. for log readability), `fromtimestamp` would return a naive local datetime and `make_aware` would mislabel it as UTC, silently shifting every Stripe timestamp into the database by the local offset. Pass `tz=timezone.utc` explicitly so the conversion no longer depends on process-local time. No behavior change on UTC hosts. Regression test forces `TZ=America/New_York` and `TZ=Asia/Tokyo` via `time.tzset()` to confirm the result is the same in any local timezone. 🤖 Generated with Claude Code
fix(stripe): redirect cancel_url to subscription page instead of home See merge request glitchtip/glitchtip-backend!2355
fix(stripe): make unix_to_datetime timezone-independent See merge request glitchtip/glitchtip-backend!2354
The HTTP path returns django-async-backend's per-asyncio-Task connection wrapper to the pool via the close_async_connections middleware. The task worker has no equivalent. When an async task uses async_connections (e.g. an ingest task reading the read replica), the wrapper's close() is never called -- the asyncio.Task ends, the wrapper is garbage collected, and psycopg.AsyncConnection.__del__ runs synchronously, which can't perform an async close handshake cleanly. Wire receivers on django_vtasks.signals.task_finished and task_failure that call async_connections.close_all() inside the same asyncio.Task that ran the user task. Mirrors the HTTP middleware semantics, including the asyncio.shield to keep cleanup running through cancellation. vtasks 2.1.1 emits exactly one of task_finished/task_failure per task (success, exception, or cancellation), so the two receivers cover every completion path. Bump the dependency floor to require it. Disclosure: prepared with assistance from Claude. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Django signal receivers receive sender/signal/extra kwargs they don't necessarily use. Rename **kwargs to **_kwargs so vulture's 100% confidence sweep skips it.
fix: close async DB connections after each background task See merge request glitchtip/glitchtip-backend!2356
django.contrib.postgres wires register_type_handlers to the connection_created signal so psycopg can auto-decode hstore columns. That handler runs `SELECT ... FROM pg_type WHERE typname = 'hstore'` on every new connection. GlitchTip uses jsonb instead of hstore, so the extra round-trip is wasted work — and the receiver uses the sync ORM, which raises SynchronousOnlyOperation when fired from inside an asyncio task. The result is that async DB connections in worker tasks raise on their first open after !2356 returned the connection-wrapper lifecycle to a healthy state. django.contrib.postgres has to stay in INSTALLED_APPS because Django's postgres.E005 check requires it for ArrayField and SearchVectorField, both of which we use. The fix is to disconnect the single offending receiver after PostgresConfig.ready() runs. Also swap the e2e test from sentry-cli to glitchtip-cli. sentry-cli is no longer open source and pulling from sentry.io is incompatible with the clean-room rules in CLAUDE.md. glitchtip-cli is a drop-in for the send-event command we use, accepts SENTRY_DSN for compatibility, and prints the event id as the last token on stdout, so the existing parser keeps working. 🤖 Generated with Claude Opus 4.7. AI-assisted; please review carefully. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fix: skip hstore OID lookup on every connection_created See merge request glitchtip/glitchtip-backend!2357
fix: make_sample_* failing because partitions in the past didn't exist See merge request glitchtip/glitchtip-backend!2358
The transaction-group spans endpoint (and other span cold queries) took
~7s. Root cause: the analytical read connection forced threads=1, so a
multi-day query read hundreds of tiny per-day chunk Parquet files from S3
one HTTP round-trip at a time, and compaction only collapsed whole days
once daily — so the recent window dashboards actually query was always
hundreds of files.
Reads:
- get_duckdb_read_connection no longer clamps to threads=1; it uses the
cgroup-aware DUCKDB_THREADS. Multi-file span aggregations are
S3-latency-bound and parallelism overlaps the round-trips.
- New process-wide duckdb_slot() semaphore bounds *all* DuckDB work
(reads, compaction, rewrite). memory_limit is per-connection, so an
unbounded request burst otherwise multiplies peak RSS. Reads degrade to
empty when saturated; must-complete deletion work blocks.
Storage tiers (spans only; logs/issue-events keep the flat layout):
- Promotion writes hour-bucketed chunks (org/{date}/{HH}/chunk_*).
- Compaction seals a completed hour into one file, lazily rolls a fully
sealed day, and is idempotent + keyed off ingestion time: a sealed file
is never rebuilt, so there is no writer race and no conservative
multi-day skip. It uses a memory-bounded DuckDB COPY (ROW_GROUP_SIZE +
memory_limit + the slot); all promotion writes stay on arro3.
- New performance_spans_rollup tier: small per-(project,txn,op,desc)
hourly aggregates emitted at hour seal. Trend queries read these
instead of raw spans, so they stay cheap over long ranges.
- Split retention: raw spans dropped at GLITCHTIP_SPAN_RAW_RETENTION_DAYS
(default 30); rollups kept for the long transaction retention.
- The transaction ingest timestamp check is back to retention + a
future-skew guard (idempotent compaction no longer needs a tight
freshness window).
Deletion (self-hosters without object-store lifecycle rules rely on it):
- Org deletion recursively purges the raw subtree (now 3 levels deep)
and the rollup tree.
- Project deletion's parquet rewrite is hour-tier aware and also strips
the project from the rollup tree.
Full performance / event_ingest / projects / organizations_ext / logs /
issue_events suites pass. The DuckDB-COPY vs arro3 compaction choice was
made by benchmarking both for speed and peak RSS under injected S3
latency (a tie; DuckDB COPY kept for far less code).
Developed with AI assistance (Claude Code). Human review required.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
So contributors adding periodic tasks (cold-storage compaction, retention sweeps, etc.) don't add an external lock to keep them from running on every pod — django-vtasks already gives single-runner semantics for scheduled entries. Manual ``aenqueue()`` calls still race and need their own guard. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Merge orphan cleanup into cleanup_old_files instead of a separate maintenance step - Use aiterator() instead of sync_to_async(list) for real async - Delete redundant cleanup_orphaned_fileblobs management command
fix: fix support for mattermost webhooks glitchtip-backend#485 See merge request glitchtip/glitchtip-backend!2434
Accept Breakpad-format debug_id in NativeDebugImage See merge request glitchtip/glitchtip-backend!2420
feat(files): periodic cleanup of orphaned FileBlobs See merge request glitchtip/glitchtip-backend!2432
Follow-ups to the orphaned-FileBlob cleanup: - Delete DB rows before storage, filtering through the original queryset so the pass conditions are re-evaluated inside the delete. Uploads dedupe on checksum via get_or_create, so a blob fetched as orphaned can gain a File reference before the batch delete runs; previously that File would be cascaded away. Row-first ordering means a skipped blob keeps both its row and its storage bytes — a crash mid-batch can only leak an unreferenced storage object, never leave a File whose backing bytes are gone. - Pass save=False to blob.delete() so it no longer issues an UPDATE blanking the blob field on an already-deleted row, halving sync-connection writes per blob. - Count only FileBlob rows against MAX_DELETIONS_PER_RUN instead of the cascade total, so the cap is a blob budget. - Log storage-delete failures with exc_info so a missing file is distinguishable from a storage misconfiguration. - Assert storage-file existence in the cleanup tests. The ORM stays on the async API throughout; only the storage delete hops threads, since django-storages has no async API. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
perf(files): streamline FileBlob cleanup deletes See merge request glitchtip/glitchtip-backend!2439
chore(deps): update dependency symbolic to ~=13.8.0 See merge request glitchtip/glitchtip-backend!2437
django-organizations 2.7.0 ships async versions of the core membership utilities. Bump to ~=2.7 and drop GlitchTip's local reimplementation of the create + user_added signal mechanics: - Organization.add_user/aadd_user now delegate org-user creation and the user_added signal to upstream via super(), keeping only the role-based first-user-becomes-owner logic (upstream's abstract variant is is_admin-based, which GlitchTip does not use). - The create-organization API calls aadd_user instead of hand-rolling the org user and owner records and sending the sync user_added signal from an async view. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Assert the creating user gets the OWNER role and an OrganizationOwner record, since that behavior now lives in aadd_user's first-user branch rather than inline in the endpoint. Also document that user_added fires before the owner record exists. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
refactor(organizations): use django-organizations 2.7 async core utils See merge request glitchtip/glitchtip-backend!2440
…endpoint Mirrors django-organizations 2.7's AbstractOrganization.achange_owner, which GlitchTip cannot inherit because Organization extends the base (not abstract) hierarchy. The set_organization_owner endpoint previously hand-rolled the owner swap and called the sync owner_changed.send() from an async view; it now delegates to achange_owner, which uses asend. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The permission fast path compared an OrganizationUser pk against a User id using identity semantics (`old_owner.pk is user_id`), so the clause that lets the current designated owner transfer ownership effectively never matched — and could coincidentally match for unrelated small ids. Compare the owner's user_id with equality instead, and pin the intended grant with a test. Also strengthen test_achange_owner to assert the owner row is updated in place rather than replaced. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace django-allauth with django-allauth-async, a fork-hosted async extension that ships pristine upstream allauth plus allauth_async — async twins of the account, headless, socialaccount and MFA flows (async ORM and sessions, aiohttp for provider HTTP, no sync_to_async thread offloads on the request path). - settings: AsyncAccountMiddleware, AsyncAuthenticationBackend, async default MFA/headless adapters - adapters: custom account/socialaccount adapters derive from the fork's async defaults; every customized sync hook now has an async twin (send_mail, is_open_for_signup, save_user), enforced at startup by the fork's twin-override guard. open_http_session applies AIOHTTP_CONFIG so provider HTTP honors PROXY_ENV and our User-Agent, as the sync stack got implicitly from requests - urls: mount allauth_async.urls and allauth_async.headless.urls (same routes/names; async views for all providers GlitchTip enables) - users: email confirmation sends via the fork's asend_confirmation; user_logged_in receiver is now natively async - recovery codes: rebuild the preview/confirm endpoints on the fork's AsyncRecoveryCodes primitives. This also fixes a latent bug where the previewed codes only matched the persisted ones when the MFA adapter's encrypt() was the default identity function, plus hardens the confirm against a concurrent-activation race - OIDC: drop glitchtip/oidc_discovery.py in favor of the fork's cached discovery helper; the settings endpoint now uses the provider's normalized well-known URL (shared cache key with the login flow) and degrades authorize_url to None on any discovery failure Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- catch ValueError alongside IntegrityError: AsyncRecoveryCodes.aactivate raises it when a concurrent confirm's row lands before the insert -- the other half of the same race, previously a 500 - namespace the cached-seed key (users:recovery-seed:<id>) instead of the bare seed<id> in the shared cache - bump django-allauth-async to 65.16.1.6, which routes the social adapter's default ais_open_for_signup through the account async twin (mirrors upstream's delegation chain; no glitchtip behavior change since our social adapter overrides both halves) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The fork release defers jwtkit's module-scope jwt/cryptography imports to the token-verification call sites, keeping both libraries out of every web and worker process at boot (~8 MiB RSS per process measured here; they only load if a JWT-verifying social provider is actually used). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
feat(organizations): add async achange_owner and use it in set_owner endpoint See merge request glitchtip/glitchtip-backend!2442
Django 6.0's ModelBackend.aauthenticate runs the user-enumeration timing mitigation (a full ~200ms password hash) synchronously on the event loop when the email doesn't exist, stalling every in-flight request. Django 6.1 fixes this (ticket #36901) but the fix is not backported, so subclass ModelBackend to offload the dummy hash to a thread, matching both Django 6.1's helper and how acheck_password offloads internally. Revert to the stock backend once the Django pin moves past 6.0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
feat(auth): switch to django-allauth-async for native-asyncio auth flows See merge request glitchtip/glitchtip-backend!2443
Long-running processes accumulate freed-but-unreturned glibc heap pages (tens of MB over hours of steady ingest). The maintenance task already runs gc.collect() + malloc_trim(0), but scheduled tasks execute once cluster-wide per interval, so only the pod that picks the task up ever gets trimmed — every other pod keeps the pages until its worker recycles. Add a small ASGI wrapper that runs the same trim on a conservative in-process timer (hourly by default, GLITCHTIP_MALLOC_TRIM_INTERVAL to tune, 0 disables). A trim costs milliseconds; a worker recycle costs a full process restart, so trimming every pod makes recycling rarer for negligible overhead. The malloc_trim helper moves to glitchtip/ memory_trim.py and the maintenance task imports it from there. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Read the interval via settings/env.int like every other knob, instead of a bare int(os.environ...) at ASGI import that would crash-loop all workers on a malformed value. - Jitter each cycle +/-50% so a fleet rolled out together doesn't run its gc pause in lockstep at the top of every hour. - Run malloc_trim in an executor: the ctypes call releases the GIL, so that half of the pause comes off the event loop for free. gc.collect() stays inline — it holds the GIL for its whole pass either way. - Move the wrapper outermost in asgi.py: the vtasks embed wrapper consumes lifespan without forwarding it, so an inner placement never starts the timer on a pod receiving no HTTP traffic. - Cancel the timer on lifespan shutdown (no pending-task destruction noise on worker recycles), name the task, and restart it if a prior event loop died. Scope the docstring honestly to ASGI processes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A postgres client connection retains buffer memory sized to the largest protocol message it ever carried — libpq grows its wire buffers to fit and never shrinks them — so long-lived pooled connections ratchet RSS upward under large-payload traffic (event and log ingest, big JSONB reads). Retiring connections periodically bounds that retention. Expose the pool's max_lifetime via DATABASE_POOL_MAX_LIFETIME (seconds, default 1800). Both database drivers accept the same pool option key and jitter the recycle deadline to avoid reconnect stampedes; setting 0 leaves the driver's own default lifetime in place. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Measurements showed the lifetime lever saturates — cutting the window 12x or 30x below the driver default landed on the same growth floor — while more frequent recycling measurably increases allocator churn (evicted connection buffers become freed-but-binned heap pages). With the bulk writes bounded at the source, there is no data supporting one pinned default over another, so leave each driver's own default in place and set max_lifetime only when DATABASE_POOL_MAX_LIFETIME is explicitly configured. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
perf(db): expose a pooled-connection max_lifetime knob See merge request glitchtip/glitchtip-backend!2450
chore(deps): update dependency symbolic to ~=13.9.0 See merge request glitchtip/glitchtip-backend!2448
perf: periodically return freed memory to the OS in every server process See merge request glitchtip/glitchtip-backend!2447
… into upstream-changes-2026-07-13
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR is auto-generated by
actions/github-script.