Weekly release: staging → main - #272
Merged
Merged
Conversation
…skills The 15.3k-char BACKEND_GENERATION_PROMPT and 10.7k-char VISUALIZATIONS_HTML_OUTPUT_FORMAT_PROMPT were re-sent in the system prompt on every LLM call. They now ship as read-only built-in skills (build-fullstack-backend, build-html-dashboard) served by SkillStore and are recalled on demand; the always-sent prompt carries short mandatory recall hints instead. create_artifact/launch_backend descriptions reinforce the recall. System prompt drops from ~43.5k to ~22.6k chars. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e source of truth for HTML contract - load() falls back to the built-in when a same-label user dir exists but is unreadable (broken shadow no longer dead-ends a mandatory contract), and logs when a user skill shadows a built-in label. - recall_skill embeds a stable marker in its payload; repeat recalls return a short stub while the body is still visible in history, and re-send the full procedure if compaction evicted it. - build-fullstack-backend step 5 no longer references the deleted VISUALIZATIONS prompt section — it recalls build-html-dashboard, the single source of truth for dashboard HTML (inline defaults only as fallback). - Hygiene: revert unrelated uv.lock resync; provenance comment/docstring/ developer docs mention built-ins; list_all/list_summaries share one _iter_skill_dirs walk. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The idempotence stub embedded the same marker _already_in_history matches on, so a stub surviving compaction (while the full body was evicted) suppressed re-sends forever. Detection now requires the marker AND the procedure header in the same message (only the full payload has both, ensure_ascii=False so the em-dash header actually matches), and the stub carries neither. Regression tests: surviving stub and marker-quoting summary both trigger a full re-send. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… publish_or_preview
…e scratchpad (ENG-824) On a host whose default code page isn't UTF-8 (GBK/cp936 on Chinese Windows, and likely other CJK locales) LocalScratchpadRuntime crashed before any cell ran: `_BOOT_SCRIPT_PATH.read_text()` decoded the boot script (which contains `…`/`—`) with the locale default → `'gbk' codec can't decode byte 0xa6`. Fix, at every parent↔child byte boundary + interpreter-level: - Read/write the boot script as UTF-8 (read_text/encode). - Force UTF-8 mode in the subprocess env (PYTHONUTF8=1 / PYTHONIOENCODING=utf-8, via _utf8_env, setdefault so an explicit override wins) — so the child's file I/O and stdio are UTF-8 regardless of host locale. - Decode/encode the cell payload + stdout/install output as UTF-8 (errors="replace" on display output so odd bytes never crash the reader). Non-breaking: this content is already UTF-8 on the wire, so UTF-8-default hosts (macOS/Linux/English Windows) are unchanged; it only turns the hard crash into working on non-UTF-8-locale hosts. Tests: _utf8_env forces UTF-8 / respects overrides; the boot script must be read as UTF-8 (its bytes are not GBK-decodable). 96 existing scratchpad tests still pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…view, ENG-824) Self-review: a bare PYTHONIOENCODING=utf-8 downgrades the child's stdio error handler from surrogateescape → strict (verified), which adds nothing over PYTHONUTF8=1 (already utf-8 for open()/filesystem/stdio) and re-introduces a strict-encode crash on exotic output. Keep only PYTHONUTF8=1. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…elf-review, ENG-824) Skill review: the main scratchpad subprocess got _utf8_env but the dependency install subprocess didn't, so on a non-UTF-8 host locale pip/uv output could come back as mojibake. Pass env=_utf8_env(os.environ) here too for consistency. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ta (folder-aware) [ENG-844]
ENG-847: Fix scratchpad web_search() on the minds-cloud gateway
…pt read (PR #253 review, ENG-824) Address Zoran's review on #253: - _setup_parent_site_packages wrote _parent_venv.pth with a plain open() (host- locale encoded) while the child reads .pth as UTF-8 under UTF-8 mode — same class of bug as the boot script. Write it as encoding="utf-8". - Extract _read_boot_script() and add a test that spies on Path.read_text to assert the boot-script read passes encoding="utf-8" — the previous bytes-only test would still pass on UTF-8 CI if the explicit encoding were dropped. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…w, ENG-824) Self-review of the review-response commit: I pinned the boot-script *read* as UTF-8 but left the sibling .pth *write* fix untested — asymmetric with the exact concern Zoran raised. Add a regression test that spies on open() and asserts the _parent_venv.pth write passes encoding="utf-8" (bypasses the heavy __init__; verified it fails if the encoding is dropped). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…d-utf8 ENG-824: force UTF-8 in the scratchpad so non-UTF-8 host locales (GBK/CJK Windows) don't crash
feat(prompts): move backend + HTML-dashboard contracts into built-in skills
feat(publish): add access modes (password/restricted) to /publish and…
…ent errors (ENG-673) (#246) * fix(llm): back off + retry mid-stream provider failures; typed transient errors (ENG-673) A mid-stream overload arrives inside an HTTP-200 stream (the SDK raises APIStatusError with status_code=200, real reason in .body), so anton's status-only classifier surfaced the nonsensical "Server returned 200 — the LLM endpoint may be temporarily unavailable" and the session loop retried it instantly with zero backoff — burning all attempts within seconds of a minutes-long incident (BUG-CM-001, Anthropic incident 2026-07-08). - New `TransientProviderError` / `ProviderOverloadedError` + a shared `classify_transient` in provider.py. Classify by BODY, not status: overloaded/api_error, 5xx, plain-429, connection drops, truncated streams. - anthropic.py: refactor the two byte-identical status-only blocks into a shared `_raise_for_status_error` mirroring the ENG-598 openai mapper; openai.py: extend that mapper with the transient branch (covers all four paths). - session.py: budget-bounded backoff-and-retry (30s/turn, cancellation-aware, jittered ~2/10/18s) for the mid-stream case that had NO prior retry; on exhaustion raise ProviderOverloadedError (carries model+provider) for the cowork-server/cowork `provider_overloaded` card. Completed tool_results are never re-executed on retry (idempotency) — only dangling tool_use is sealed. - Split by prior-retry: request-time 5xx/429/connection errors (already SDK-retried) and truncated streams carry session_backoff=False — honest typed message, but fail fast instead of stacking another 30s. - Log the (scrubbed, via ENG-583 scrub_credentials) error body on every transient occurrence. Tests: tests/test_transient_retry.py (classifier, both mappers, backoff helpers, turn-level recovery / budget-exhaustion / cancel / no-replay). Updated the two ENG-598 mapper tests (429/500 now typed-transient) and the two e2e error-handling tests (honest message, fast fail). Full suite green except the 2 pre-existing environmental scratchpad-subprocess failures. Part 1 of 3 for ENG-673 (cowork-server + cowork companions to follow). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(llm): address adversarial-review findings on the transient-retry path (ENG-673) Self-review of the 3-PR stack surfaced four issues; fixing them here (anton side): - #1 (was a real regression): truncation detection raised whenever a stream ended with no finish_reason/stop_reason — but many OpenAI-compatible endpoints simply don't report one, so a COMPLETE, good answer was being discarded and turned into an error (and would fail every turn for such a provider). Now only the truly-empty case (no content AND no tool_calls) is treated as truncated; a content-bearing stream without a terminal marker is logged and passed through. - #3: user-stop DURING backoff re-raised the TransientProviderError, surfacing a provider-error card instead of a clean cancellation. Now it breaks cleanly (like a normal stop). - #4: ProviderOverloadedError always named the planning model even when the CODING model was the one that failed. TransientProviderError now carries the in-flight `model` (threaded through classify_transient + both providers' raise sites); the card names the actual failing model, falling back to planning. Tests updated for the new clean-cancel semantics + 2 new (model propagation, failing-model-not-planning). Full suite green (bar the 2 pre-existing environmental scratchpad failures). (#2 — an overstated "graceful degradation" claim — corrected in the PR #246 description, no code change.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(llm): regression guards for the truncation fix — content-without-finish_reason passes through, empty stream truncates (ENG-673) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(session): don't tell the model to "adjust your approach" on a provider blip (ENG-673 #6) A request-time TransientProviderError (5xx / rate-limit / dropped connection) reaching the count-based retry path was injected as "An error interrupted execution… adjust your approach to avoid the same error" — but that's a service hiccup, not the model's fault, so the note misattributes the failure and can degrade the next attempt mid-incident. Transient errors now get a neutral note ("a transient service issue, not a problem with your approach — continue as planned"); genuine errors keep the original recovery guidance. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(llm): recover mid-stream connection/APIError failures; keep empty-truncated fail-fast (ENG-673) Review-round fixes (Sam/SailingSF, anton#246): - session_backoff now means "did the SDK already retry?" — mid-stream failures (connection drop after the 200, read timeout) back off within the budget; request-establishment failures still fail fast. Adds a stream_started flag on all three streaming paths (anthropic, openai chat.completions, Responses API). - Catch the bare openai.APIError a mid-stream SSE error raises (it is NOT an APIStatusError) so the OpenAI/MindsHub path classifies + backs off instead of leaking a generic error; classify_transient now reads both the Anthropic (nested) and OpenAI (unwrapped, top-level) body dialects. - Empty-from-start truncated stream stays fail-fast: a broken/misconfigured endpoint must not loop the 30s budget (product decision; carved out of the ticket's "truncated -> recover"). - Real-SDK mock harness (tests/test_transient_retry_e2e.py) over httpx.MockTransport; resolves the dangling test_transient_retry_e2e reference. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ENG-742: Count turns without tools-calls
…-independent UTF-8) (#263) * fix(scratchpad): read/write scratchpad + chat files as explicit UTF-8 (ENG-940) Completes ENG-824's Fix #2 ("suspenders") that the belt-only fix left undone. The scratchpad/chat-path reads relied entirely on PYTHONUTF8 being set by the launcher, so any path that misses it (bare CLI, provisioned/Docker cowork, OpenClaw) re-crashed on a GBK/CJK Windows host at `code = script_path.read_text()`. Add explicit encoding="utf-8" to every text read/write in that path so they're launcher-independent (belt AND suspenders, as ENG-824 specced): - chat.py: script read (the root-caused crash site), .env read + append, published/legacy/pub_file JSON read + write. - core/backends/local.py: .python_version + requirements.txt read + write. Tests (tests/test_scratchpad_utf8.py): a real non-ASCII fixture proves the explicit UTF-8 read round-trips while a host-locale (GBK) read crashes-or- corrupts (payload-independent); a regression guard fails if any `.read_text()` in anton/chat.py drops encoding=. Both independent of PYTHONUTF8. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(scratchpad): surrogatepass the cell-payload encode — encode-side sibling of ENG-824 (ENG-940) Follow-up from ENG-940's new evidence (users sabrina/eddie/janis): a non-ASCII Windows path (pt-BR "Área de Trabalho", emoji filename) is surrogate-escaped into lone surrogates (\udcXX) when decoded on a non-UTF-8 host. When that string reaches the strict UTF-8 encode of the cell payload sent to the subprocess, it raises "UnicodeEncodeError: surrogates not allowed" and kills the whole session — the encode-side sibling of ENG-824's decode crash. - local.py: extract _encode_cell_payload() using errors="surrogatepass" so the host-side encode can't crash when the host isn't in UTF-8 mode; the subprocess (always UTF-8 mode) decodes the payload fine. - The two chat.py JSON writes need NO change: json.dumps defaults to ensure_ascii=True, so surrogates become ASCII \uXXXX escapes and never reach the encoder (verified). - tests: pin the surrogate-safe encode (strict raises, surrogatepass round-trips) and a broadened accented-Latin + emoji case that must pass through unmangled. Verified the surrogate test fails if the helper reverts to strict. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(scratchpad): use surrogateescape (not surrogatepass) for the cell-payload encode (ENG-940 self-review) Adversarial review of de6d6fb caught a real bug in my own fix. surrogatepass does NOT round-trip through the subprocess: the subprocess always runs in UTF-8 mode, so its stdin decodes with surrogateescape — and surrogatepass emits the 3-byte CESU form that surrogateescape then re-mangles (\udc81 -> three surrogates), so the path would not survive intact. surrogateescape is correct on both counts: it's the inverse of the os.fsdecode that created these lone surrogates (U+DC80..U+DCFF), so it restores the original path bytes, and it matches the subprocess's surrogateescape stdin decode — the path arrives verbatim. Verified end-to-end. Also fixes the test, which previously asserted a false surrogatepass/surrogatepass symmetry; it now decodes with surrogateescape (what the subprocess actually does) and genuinely discriminates — it fails if the helper reverts to surrogatepass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(scratchpad): heal lone surrogates before compile() (ENG-981)
The scratchpad child crashes at `compile(code, "<scratchpad>", "exec")` when the
cell source carries a lone surrogate (\udcXX) — a non-ASCII Windows path byte
(e.g. "Área de Trabalho", an emoji filename) surrogate-escaped upstream and
passed through the belt's lenient surrogateescape stdin. compile() strict-encodes
the source and rejects the surrogate ("surrogates not allowed"), killing the cell
before any user code runs. Verified on 3 real desktop users (sabrina/eddie/tim).
This is a distinct site from ENG-824 (GBK decode) and ENG-940 (chat.py read
encoding); neither fixes it, and the ENG-824 belt (PYTHONUTF8) actually *enables*
it (the lenient stdin creates the surrogate; strict compile() then rejects it).
Fix: heal_surrogate_source() in wire.py, called in scratchpad_boot.py right
before compile(). It re-encodes via surrogateescape and decodes as UTF-8, which
*reassembles* the correct character when the surrogates are the byte-escaped
halves of a real multibyte char (the common path case — so the cell compiles AND
opens the right file), and falls back to a replacement-char scrub for a
genuinely-lone surrogate (compile succeeds instead of crashing). No-op on clean
source. Lives in wire.py so both the child and parent can share it.
Also corrects two ENG-940 (#263) tests that overclaimed: _encode_cell_payload's
surrogateescape transport only makes the pipe lossless — it does NOT fix the
compile() crash (that was input-dependent relocation). Relabeled to state they
pin transport fidelity only; the real fix is the heal tested here.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(scratchpad): heal recoverable chars in mixed cells (surrogateescape + replace) — hard-review finding (ENG-981)
Hard review caught a correctness gap in the heal: the recover path used a strict
decode with a full surrogatepass-scrub fallback, so a MIXED cell (a recoverable
byte-escaped path + an unrelated lone byte) raised on the lone byte and fell to
the full scrub — mojibako-ing the recoverable "Área" too.
Use errors="replace" on the surrogateescape decode instead: recoverable
multibyte chars are reassembled and stray bytes scrubbed to U+FFFD in the SAME
pass, so a recoverable path survives next to an unrecoverable byte. The
surrogatepass fallback now only guards the encode-raise (high/unpaired
surrogates outside DC80..DCFF). Still total (never raises), still no residual
surrogate. Adds a mixed-cell regression test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(scratchpad): pre-scrub unmappable surrogates before recover (pnewsam review, ENG-981)
Review caught a real mixed-cell data-loss path I missed: a high/unpaired
surrogate (outside DC80..DCFF, e.g. \ud800) makes the surrogateescape *encode*
raise, so the whole cell fell to the surrogatepass full-scrub — destroying the
recoverable byte-escaped "Á" elsewhere in the same cell (C:/Área -> C:/������rea).
This is the same data-loss class the previous commit fixed for the decode side.
Fix: two steps. (1) Replace surrogates outside the escapable DC80..DCFF range
with U+FFFD up front, so one unmappable surrogate can't drag recoverable ones
into a lossy path. (2) surrogateescape-encode + replace-decode the rest — now it
can never raise, so the try/except is gone entirely. Recoverable DC80..DCFF
byte-escapes survive next to any unmappable surrogate.
Adds test_heal_preserves_recoverable_char_beside_unmappable_surrogate. Also fixes
the stale surrogatepass/framing in the ENG-940 transport-test header comment
(minor, same review).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fix(prompts): reference created artifact in final message
ENG 648: A public-data-sources skill
ENG-660: Use route model for summarization
fix(cli): flush per-round preambles separately instead of concatenating
fix for eng-845
ENG-648: cheap front-model thalamus gate (respond-or-delegate), off by default
fix: npm packages vulnerability
fix: pillow vulnerability
Add a terminal notify-failure job (if: failure(), needs every job so a mid-graph failure still fires) to every push-to-main and push-to-staging pipeline and to the freeze/unfreeze wrappers, plus a paired recovery job on the pipelines. All route to the eng channel via the shared notify-main-failure reusable.
…275) Refactor CI workflows to utilize shared reusable workflows for failure and recovery notifications. Simplifies job definitions and ensures consistent alert behavior across build, release, and test pipelines.
There was a problem hiding this comment.
Pull request overview
Weekly staging → main release bringing multiple reliability, UX, and operational hardening changes across publishing, LLM error handling, scratchpad encoding, skill packaging, and CI workflows.
Changes:
- Add transient provider error classification with budgeted backoff/retry, plus new thalamus “router” gating and router-role summarization.
- Harden scratchpad/chat paths for locale-independent UTF‑8 behavior and heal lone-surrogate sources before
compile(). - Introduce unified publish access/target resolution (incl. access modes) and ship built-in skills/contracts; add CI/release/docs notification jobs.
Reviewed changes
Copilot reviewed 52 out of 54 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/test_unpublish_clears_local.py | Adds regression coverage for clearing local publish map on /unpublish. |
| tests/test_transient_retry.py | Unit tests for transient classification, provider mappers, and session backoff/idempotency. |
| tests/test_transient_retry_e2e.py | E2E tests against real SDK stream parsers via httpx.MockTransport. |
| tests/test_thalamus.py | Tests for history condensation, gate decisions, and ChatSession integration. |
| tests/test_status_error_mapper.py | Updates status error mapper expectations for typed transient errors. |
| tests/test_settings.py | Tests router provider normalization and defaults. |
| tests/test_scratchpad_utf8.py | Regression suite for UTF‑8 mode + explicit encodings + surrogate healing. |
| tests/test_scratchpad_flavor.py | Tests web_search flavor resolution for Minds gateway vs OpenAI. |
| tests/test_publish_tool_access.py | Tests publish_or_preview access-mode handling and target normalization. |
| tests/test_publish_chat_access.py | Ensures /publish passes chosen access to publisher and persists it. |
| tests/test_publish_api_key.py | Updates /publish prompt flow expectations (now includes access mode). |
| tests/test_publish_access_resolve.py | New tests for unified access + publish-target resolution helpers. |
| tests/test_client.py | Tests router-role summarization/gate wiring in LLMClient. |
| tests/test_chat_ui.py | Updates UI tests for new streaming buffer/preamble flush behavior. |
| tests/test_builtin_skills.py | Verifies shipped built-in skills, shadowing, and recall idempotence. |
| tests/e2e/scenarios/test_error_handling.py | Adjusts e2e expectations for new transient error messaging. |
| tests/conftest.py | Adds autouse fixture to isolate built-in skills unless explicitly requested. |
| README.md | Documents shared CalVer release workflow usage. |
| pyproject.toml | Bumps Pillow lower bound to address vulnerability. |
| docs/package-lock.json | Updates multiple JS dependency versions for security fixes. |
| docs/docs/developer/skills-internals.md | Documents built-in skills root behavior and shadowing rules. |
| anton/tools.py | Enhances publish_or_preview to resolve targets/access + preview folders. |
| anton/publish_access.py | Adds single-source-of-truth access + publish target resolution. |
| anton/memory/history_store.py | Adds is_user_turn() helper to refine user-turn counting. |
| anton/core/tools/tool_defs.py | Adds skill prerequisite guidance to artifact/backend tool descriptions. |
| anton/core/tools/recall_skill.py | Adds stable recall marker + improved idempotence/compaction handling. |
| anton/core/settings.py | Introduces router settings (router_enabled, router_max_tokens). |
| anton/core/session.py | Adds router gating, router summarization call, transient backoff budget, and turn counting fixups. |
| anton/core/memory/skills.py | Adds built-in skills root, shadowing, and builtin-safe listing behavior. |
| anton/core/memory/builtin_skills/public-data-sources/SKILL.md | Ships public data catalog as a built-in skill. |
| anton/core/memory/builtin_skills/build-html-dashboard/SKILL.md | Ships HTML dashboard output contract as a built-in skill. |
| anton/core/memory/builtin_skills/build-fullstack-backend/SKILL.md | Ships backend/fullstack contract as a built-in skill. |
| anton/core/llm/thalamus.py | Implements thalamus gate: condensed history + respond/delegate decision. |
| anton/core/llm/provider.py | Adds typed transient/provider-overloaded errors + shared classifier. |
| anton/core/llm/prompt_builder.py | Stops formatting backend prompt with output_dir; points to skill instead. |
| anton/core/llm/openai.py | Adds transient classification/logging + mid-stream error handling + flavor resolution. |
| anton/core/llm/client.py | Adds router role (summarize/gate) with coding-role fallback. |
| anton/core/llm/anthropic.py | Refactors status mapping + transient classification + truncation handling. |
| anton/core/datasources/data_vault.py | Uses atomic Path.replace() instead of rename(). |
| anton/core/backends/wire.py | Adds lone-surrogate healing helper for scratchpad code. |
| anton/core/backends/scratchpad_boot.py | Uses web flavor resolver + heals surrogates before compile(). |
| anton/core/backends/local.py | Forces UTF‑8 mode/encodings across scratchpad boot/payload/install I/O. |
| anton/config/settings.py | Adds router provider/model fields + provider normalization for router role. |
| anton/commands/session.py | Uses refined user-turn counting when restoring sessions. |
| anton/chat.py | Adds publish target/access resolution + clears local publish map on unpublish; UTF‑8 reads/writes. |
| anton/chat_ui.py | Simplifies streaming buffer; flushes preambles at tool boundaries. |
| .github/workflows/tests.yml | Adds push-only failure/recovery notifications. |
| .github/workflows/staging-unfreeze.yml | Adds failure notifications for unfreeze/sync-back. |
| .github/workflows/staging-freeze.yml | Adds failure notifications for freeze. |
| .github/workflows/release.yml | Switches to shared CalVer workflow + adds pipeline notifications. |
| .github/workflows/docs.yml | Adds docs build/deploy failure/recovery notifications. |
| .github/workflows/cla.yml | Switches CLA to shared reusable workflow. |
Files not reviewed (1)
- docs/package-lock.json: Generated file
Comments suppressed due to low confidence (1)
anton/tools.py:588
- Persisting
.published.jsonuseswrite_text(...)withoutencoding='utf-8'. This can reintroduce locale-dependent behavior and also makes read/write symmetry inconsistent with/publish(which writes UTF-8 explicitly).
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…rting Add a scheduled workflow to detect and alert on deploy pipeline startup failures that prevent branch deployments. Configure actionlint for organization-specific self-hosted runner labels to reduce noise in error reporting.
lucas-koontz
approved these changes
Jul 27, 2026
Add explicit `contents: read` and `actions: read` permissions to `staging-freeze` and `staging-unfreeze` workflows. This ensures compatibility with reusable workflows and prevents loading failures due to insufficient permissions.
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 subscribe to this conversation on GitHub.
Already have an account?
Sign in.
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.
Weekly release: staging → main
45 commit(s) ready for production release.
Changes
Contributors
Review checklist