Skip to content

fix: cache action inputs in actions-lock.json for deterministic smoke-codex compilation#22048

Merged
pelikhan merged 4 commits intomainfrom
copilot/fix-smoke-codex-compilation
Mar 21, 2026
Merged

fix: cache action inputs in actions-lock.json for deterministic smoke-codex compilation#22048
pelikhan merged 4 commits intomainfrom
copilot/fix-smoke-codex-compilation

Conversation

Copy link
Contributor

Copilot AI commented Mar 20, 2026

Summary

Fixes the compiler so smoke-codex.lock.yml stops alternating between two forms on different branches.

Root Cause

The compiler fetches action.yml from GitHub at compile time to derive the input schema for custom safe-output actions. When the network/auth is available it produces a specific-inputs form (additionalProperties: false, with labels and number fields). When the fetch fails (no auth, network timeout), it falls back to a generic payload field (additionalProperties: true). This makes the lock file non-deterministic.

Fix

Extended ActionCacheEntry (in actions-lock.json) with an inputs field. The compiler now resolves inputs using the following priority:

  1. Inputs explicitly specified in the frontmatter (new escape hatch, highest priority)
  2. Inputs cached in actions-lock.json
  3. Inputs fetched from the remote action.yml (result stored in cache for future deterministic compilations)

The action reference is always pinned to a commit SHA for security.

Changes

  • pkg/workflow/action_cache.go: Added Inputs field to ActionCacheEntry; updated Set() to preserve existing cached inputs; added GetInputs() and SetInputs() methods
  • pkg/workflow/safe_outputs_actions.go: fetchAndParseActionYAML checks the ActionCache for cached inputs before making a network request; on a successful network fetch, inputs are stored in the cache
  • pkg/parser/schemas/main_workflow_schema.json: Added inputs field to the action configuration schema (frontmatter escape hatch)
  • .github/aw/actions-lock.json + pkg/workflow/data/action_pins.json: Added inputs for actions-ecosystem/action-add-labels@v1
  • pkg/workflow/safe_outputs_actions_test.go: Added TestParseActionsConfigWithInputs test
  • pkg/workflow/action_cache_test.go: Added TestActionCacheInputs test
  • .github/workflows/smoke-codex.lock.yml: Recompiled using cached inputs

Security Summary

No security vulnerabilities introduced or found. CodeQL analysis passed with 0 alerts.


⚡ Quickly spin up Copilot coding agent tasks from anywhere on your macOS or Windows machine with Raycast.



✨ PR Review Safe Output Test - Run 23368377758

Note

🔒 Integrity filtering filtered 2 items

Integrity filtering activated and filtered the following items during workflow execution.
This happens when a tool call accesses a resource that does not meet the required integrity or secrecy level of the workflow.

💥 [THE END] — Illustrated by Smoke Claude ·

…istic compilation

When `inputs` are specified in the workflow frontmatter for a custom
safe-output action, the compiler now uses them directly and skips the
network fetch of action.yml. This prevents the lock file from alternating
between the specific-inputs form and the generic-payload fallback form
depending on network/auth availability at compile time.

- Add `inputs` parsing in parseActionsConfig
- Skip action.yml network fetch when inputs are pre-specified
- Update JSON schema to allow `inputs` field on actions
- Add explicit inputs to smoke-codex.md for add-smoked-label
- Add TestParseActionsConfigWithInputs test

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Agent-Logs-Url: https://github.com/github/gh-aw/sessions/35231559-de42-47fb-97b6-4899334142a5
Copy link
Contributor

@pelikhan pelikhan left a comment

Choose a reason for hiding this comment

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

@copilot revert modifications to smoke-copilot.

Update actions-lock to cache the inputs of the action used in the safe output so that the compilation is determinstic even with network data issues.

…lation

Instead of requiring users to specify action inputs in the workflow
frontmatter, extend the ActionCache (actions-lock.json) to store inputs
alongside the SHA. Compilation now uses cached inputs when available,
falling back to a network fetch only when needed, and storing the result
for future deterministic compilations.

- Revert smoke-codex.md to original (no manually-added inputs)
- Add Inputs field to ActionCacheEntry in action_cache.go
- Update Set() to preserve existing cached inputs
- Add GetInputs() and SetInputs() methods
- Update fetchAndParseActionYAML to check cache before network
- Add inputs for actions-ecosystem/action-add-labels@v1 to actions-lock.json
- Add TestActionCacheInputs test

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Agent-Logs-Url: https://github.com/github/gh-aw/sessions/ca74d9bc-5381-4c9d-b8c6-8a89e7175593
Copy link
Contributor Author

Copilot AI commented Mar 21, 2026

@copilot revert modifications to smoke-copilot.

Update actions-lock to cache the inputs of the action used in the safe output so that the compilation is determinstic even with network data issues.

Done in 0636976. Reverted the smoke-codex.md changes and instead extended ActionCacheEntry (in actions-lock.json) with an inputs field. The actions-ecosystem/action-add-labels@v1 entry now includes its inputs in the cache. The compiler's fetchAndParseActionYAML checks the cache first; on a cache miss it fetches from the network and stores the result for deterministic future compilations.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • https://api.github.com/graphql
    • Triggering command: /usr/bin/gh /usr/bin/gh api graphql -f query=query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { hasDiscussionsEnabled } } -f owner=github -f name=gh-aw (http block)
  • https://api.github.com/repos/astral-sh/setup-uv/git/ref/tags/eac588ad8def6316056a12d4907a9d4d84ff7a3b
    • Triggering command: /usr/bin/gh gh api /repos/astral-sh/setup-uv/git/ref/tags/eac588ad8def6316056a12d4907a9d4d84ff7a3b --jq .object.sha (http block)
  • https://api.github.com/repos/githubnext/agentics/git/ref/tags/
    • Triggering command: /usr/bin/gh gh api /repos/githubnext/agentics/git/ref/tags/# --jq .object.sha (http block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot AI changed the title fix: allow specifying action inputs in frontmatter to ensure deterministic smoke-codex compilation fix: cache action inputs in actions-lock.json for deterministic smoke-codex compilation Mar 21, 2026
Copilot AI requested a review from pelikhan March 21, 2026 00:04
@github-actions github-actions bot mentioned this pull request Mar 21, 2026
@pelikhan pelikhan marked this pull request as ready for review March 21, 2026 00:38
Copilot AI review requested due to automatic review settings March 21, 2026 00:38
@pelikhan
Copy link
Contributor

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR aims to make smoke-codex.lock.yml compilation deterministic by avoiding network-dependent alternation in derived action input schemas, using cached inputs and an explicit frontmatter override.

Changes:

  • Adds an inputs escape hatch to safe-output action configuration (frontmatter + schema).
  • Extends the action cache (actions-lock.json) to persist action input definitions and uses them before network fetches.
  • Updates lock/pin data and adds tests covering input parsing and cache behavior.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
pkg/workflow/safe_outputs_actions.go Adds frontmatter inputs parsing and implements input-resolution priority (frontmatter → cache → network).
pkg/workflow/safe_outputs_actions_test.go Adds test ensuring inputs in actions config are parsed correctly.
pkg/workflow/action_cache.go Extends cache entries with Inputs and adds Get/Set APIs for cached inputs.
pkg/workflow/action_cache_test.go Adds test coverage for caching/retrieving inputs and preserving them across Set().
pkg/parser/schemas/main_workflow_schema.json Adds safe-outputs.actions.*.inputs schema for deterministic/offline compilation.
pkg/workflow/data/action_pins.json Adds inputs data to embedded pins file.
.github/aw/actions-lock.json Adds cached inputs for actions-ecosystem/action-add-labels@v1.
.github/workflows/smoke-codex.lock.yml Updates compiled metadata hash after recompilation.

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

if cachedInputs, ok := data.ActionCache.GetInputs(ref.Repo, ref.Ref); ok {
safeOutputActionsLog.Printf("Using cached inputs for %q (%s@%s)", actionName, ref.Repo, ref.Ref)
config.Inputs = cachedInputs
// Skip the remote fetch; inputs are already populated.
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

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

When cached inputs are found, the code skips fetching action.yml entirely and never sets config.ActionDescription. This changes tool/step display names and tool descriptions compared to the network path. Consider caching the action description alongside inputs (or still fetching/parsing description when Inputs come from cache) so offline deterministic compiles keep the same descriptions.

Suggested change
// Skip the remote fetch; inputs are already populated.
// Inputs are populated from cache; still fetch action.yml to populate description
// so that cached and non-cached paths produce consistent tool/step descriptions.
actionYAML, err = fetchRemoteActionYAML(ref.Repo, ref.Subdir, fetchRef)
if err != nil {
safeOutputActionsLog.Printf("Warning: failed to fetch action.yml for description for %q (%s): %v", actionName, config.Uses, err)
} else if actionYAML != nil && actionYAML.Description != "" {
config.ActionDescription = actionYAML.Description
}

Copilot uses AI. Check for mistakes.
// 2. Inputs cached in the ActionCache (actions-lock.json)
// 3. Inputs fetched from the remote action.yml (result cached for future runs)
//
// The action reference is always pinned to a commit SHA for security.
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

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

The comment says the action reference is always pinned to a commit SHA, but GetActionPinWithData can return an empty pinned ref (no pin available) and the code then falls back to using config.Uses at step-generation time. Consider rewording this comment to reflect the actual behavior (pinned when available, otherwise falls back).

Suggested change
// The action reference is always pinned to a commit SHA for security.
// When available, the action reference is pinned to a commit SHA for security;
// if no pin is available, later step generation falls back to the original config.Uses.

Copilot uses AI. Check for mistakes.
Comment on lines +213 to +222
// Preserve any previously-cached inputs so that SetInputs calls are not lost
// when Set is called later (e.g. when the SHA is refreshed from the network).
// When no entry exists, `existing` is the zero value and existing.Inputs is nil,
// so the new entry simply has no cached inputs — which is the correct behavior.
existing := c.Entries[key]
c.Entries[key] = ActionCacheEntry{
Repo: repo,
Version: version,
SHA: sha,
Inputs: existing.Inputs,
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

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

ActionCache.Set() now always preserves existing cached Inputs even when the SHA for the same repo@version key changes. If a moving tag (e.g., v1/v4) is refreshed to a new SHA, this can leave Inputs describing the old commit while the workflow is pinned to the new SHA. Consider clearing Inputs when the stored SHA changes, or keying inputs by SHA so they stay consistent with the pinned commit.

Suggested change
// Preserve any previously-cached inputs so that SetInputs calls are not lost
// when Set is called later (e.g. when the SHA is refreshed from the network).
// When no entry exists, `existing` is the zero value and existing.Inputs is nil,
// so the new entry simply has no cached inputs — which is the correct behavior.
existing := c.Entries[key]
c.Entries[key] = ActionCacheEntry{
Repo: repo,
Version: version,
SHA: sha,
Inputs: existing.Inputs,
// Preserve previously-cached inputs only when the SHA is unchanged. If the SHA
// changes (e.g. for a moving tag that now points to a new commit), drop any
// existing inputs so they stay consistent with the pinned commit.
existing := c.Entries[key]
var inputs map[string]*ActionYAMLInput
if existing.SHA == sha {
inputs = existing.Inputs
} else if existing.Inputs != nil {
actionCacheLog.Printf("Clearing cached inputs for key=%s due to SHA change (%s -> %s)", key, existing.SHA, sha)
}
c.Entries[key] = ActionCacheEntry{
Repo: repo,
Version: version,
SHA: sha,
Inputs: inputs,

Copilot uses AI. Check for mistakes.
Comment on lines +241 to +247
// If no cache entry exists for the key, this call is a no-op.
func (c *ActionCache) SetInputs(repo, version string, inputs map[string]*ActionYAMLInput) {
key := formatActionCacheKey(repo, version)
entry, exists := c.Entries[key]
if !exists {
actionCacheLog.Printf("No cache entry for key=%s, skipping SetInputs", key)
return
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

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

SetInputs is currently a no-op when the cache entry is missing. That means a successful action.yml fetch may not actually persist inputs unless some earlier step already created the cache entry. Consider having SetInputs create the entry (with repo/version and existing SHA if known), or ensuring callers always create/set the entry before calling SetInputs so the determinism guarantee holds.

Suggested change
// If no cache entry exists for the key, this call is a no-op.
func (c *ActionCache) SetInputs(repo, version string, inputs map[string]*ActionYAMLInput) {
key := formatActionCacheKey(repo, version)
entry, exists := c.Entries[key]
if !exists {
actionCacheLog.Printf("No cache entry for key=%s, skipping SetInputs", key)
return
// If no cache entry exists for the key, a new entry is created (with an empty SHA).
func (c *ActionCache) SetInputs(repo, version string, inputs map[string]*ActionYAMLInput) {
key := formatActionCacheKey(repo, version)
entry, exists := c.Entries[key]
if !exists {
actionCacheLog.Printf("No cache entry for key=%s, creating new entry for inputs", key)
entry = ActionCacheEntry{
Repo: repo,
Version: version,
}

Copilot uses AI. Check for mistakes.
},
"additionalProperties": false
}
}
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

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

The new safe-outputs.actions.*.inputs schema uses patternProperties but does not set additionalProperties:false on the inputs object itself. As a result, keys that don't match the input-name regex are still permitted by default. Consider adding additionalProperties:false at the inputs object level to enforce the intended key pattern.

Suggested change
}
},
"additionalProperties": false

Copilot uses AI. Check for mistakes.
Comment on lines +6 to +26
"sha": "18f1af5e3544586314bbe15c0273249c770b2daf",
"inputs": {
"github-token": {
"description": "The GitHub token for authentication.",
"required": false,
"default": "${{ github.token }}"
},
"labels": {
"description": "The labels' name to be added. Must be separated with line breaks if there're multiple labels.",
"required": true
},
"number": {
"description": "The number of the issue or pull request.",
"required": false
},
"repo": {
"description": "The owner and name of the repository. Defaults to the current repository.",
"required": false,
"default": "${{ github.repository }}"
}
}
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

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

This JSON now includes an "inputs" field, but the Go types used to unmarshal embedded action_pins.json (ActionPin / ActionPinsData in pkg/workflow/action_pins.go) do not include Inputs, so the data is ignored at runtime. Either remove "inputs" from this file, or update the pin data model and pinning logic to actually consume it.

Suggested change
"sha": "18f1af5e3544586314bbe15c0273249c770b2daf",
"inputs": {
"github-token": {
"description": "The GitHub token for authentication.",
"required": false,
"default": "${{ github.token }}"
},
"labels": {
"description": "The labels' name to be added. Must be separated with line breaks if there're multiple labels.",
"required": true
},
"number": {
"description": "The number of the issue or pull request.",
"required": false
},
"repo": {
"description": "The owner and name of the repository. Defaults to the current repository.",
"required": false,
"default": "${{ github.repository }}"
}
}
"sha": "18f1af5e3544586314bbe15c0273249c770b2daf"

Copilot uses AI. Check for mistakes.
SHA string `json:"sha"`
Inputs map[string]*ActionYAMLInput `json:"inputs,omitempty"` // cached inputs from action.yml
}

Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

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

ActionCacheEntry.Inputs is persisted to actions-lock.json using encoding/json, but ActionYAMLInput currently has no json struct tags. This will cause newly-written cache files to emit fields like "Description"/"Required"/"Default" (capitalized) rather than the existing lowercase keys ("description", "required", "default") used in the repo JSON. Consider adding json tags to ActionYAMLInput (or a dedicated cache DTO) to keep the on-disk format stable and deterministic.

Suggested change
// actionInputDTO is a dedicated cache representation for ActionYAMLInput
// that preserves the existing lowercase JSON keys used on disk.
type actionInputDTO struct {
Description string `json:"description,omitempty"`
Required bool `json:"required,omitempty"`
Default string `json:"default,omitempty"`
}
// MarshalJSON customizes JSON encoding for ActionCacheEntry to ensure that
// cached inputs are written using lowercase keys ("description",
// "required", "default") to keep the on-disk format stable.
func (e ActionCacheEntry) MarshalJSON() ([]byte, error) {
// Build a DTO for serialization that mirrors the existing cache layout.
type cacheEntryDTO struct {
Repo string `json:"repo"`
Version string `json:"version"`
SHA string `json:"sha"`
Inputs map[string]*actionInputDTO `json:"inputs,omitempty"`
}
dto := cacheEntryDTO{
Repo: e.Repo,
Version: e.Version,
SHA: e.SHA,
}
if len(e.Inputs) > 0 {
dto.Inputs = make(map[string]*actionInputDTO, len(e.Inputs))
for name, input := range e.Inputs {
if input == nil {
continue
}
dto.Inputs[name] = &actionInputDTO{
Description: input.Description,
Required: input.Required,
Default: input.Default,
}
}
}
return json.Marshal(dto)
}

Copilot uses AI. Check for mistakes.
@pelikhan
Copy link
Contributor

@copilot review comment

- Add JSON tags to ActionYAMLInput (description, required, default)
  so inputs serialized to actions-lock.json use lowercase keys
- Fix Set() to clear cached inputs when SHA changes (stale inputs
  would describe a different commit)
- Fix SetInputs() to create entry if none exists (was silently
  dropping network-fetched inputs when no cache entry existed)
- Add ActionDescription field to ActionCacheEntry to cache action
  description alongside inputs for consistent offline compilation
- Add GetActionDescription/SetActionDescription methods
- Update fetchAndParseActionYAML to restore and cache description
- Add Inputs field to ActionPin so action_pins.json data is parsed
  rather than silently ignored by the JSON decoder
- Add additionalProperties: false to inputs schema object
- Fix actions-lock.json to use lowercase JSON keys for inputs
- Update TestActionCacheInputs to match new SHA-change/create-entry behavior

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Agent-Logs-Url: https://github.com/github/gh-aw/sessions/54823ddb-6e1b-4c40-ad02-b2d62756aea2
Copy link
Contributor Author

Copilot AI commented Mar 21, 2026

@copilot fix tests https://github.com/github/gh-aw/actions/runs/23368141084/job/67986288345?pr=22048 review comments

Fixed in 08384da. Addressed all 7 review comments:

  • Added JSON tags to ActionYAMLInput (description/required/default) so the cache file uses lowercase keys
  • Fixed Set() to clear inputs when the SHA changes (stale inputs for a new commit)
  • Fixed SetInputs() to create a new entry when none exists (was silently dropping network-fetched inputs)
  • Added ActionDescription to ActionCacheEntry and cache/restore it alongside inputs
  • Added Inputs field to ActionPin so data in action_pins.json is properly parsed
  • Added additionalProperties: false to the inputs schema object
  • Fixed actions-lock.json to use lowercase JSON keys

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • https://api.github.com/graphql
    • Triggering command: /usr/bin/gh /usr/bin/gh api graphql -f query=query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { hasDiscussionsEnabled } } -f owner=github -f name=gh-aw crypto (http block)
    • Triggering command: /usr/bin/gh /usr/bin/gh api graphql -f query=query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { hasDiscussionsEnabled } } -f owner=github -f name=gh-aw crypto/internal/--norc -lang=go1.25 x_amd64/vet -I g_.a -I x_amd64/vet --gdwarf-5 --64 -o x_amd64/vet (http block)
    • Triggering command: /usr/bin/gh /usr/bin/gh api graphql -f query=query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { hasDiscussionsEnabled } } -f owner=github -f name=gh-aw crypto/internal/-V=full -lang=go1.25 x_amd64/vet -I 0755232/b187/_pkg_.a -I x_amd64/vet --gdwarf-5 g/x/crypto/crypt--norc -o x_amd64/vet (http block)
  • https://api.github.com/repos/actions/setup-node/git/ref/tags/v6
    • Triggering command: /usr/bin/gh gh api /repos/actions/setup-node/git/ref/tags/v6 --jq .object.sha -I .cfg $name) { hasDiscussionsEnabled } } . --gdwarf2 --64 64/pkg/tool/linux_amd64/vet 0755�� 0755232/b286/_pkg_.a /tmp/go-build1110755232/b216/ 0/x64/bin/node . bracelet/x/ansi/image --64 0/x64/bin/node (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/setup-node/git/ref/tags/v6 --jq .object.sha E=3 .cfg $name) { hasDiscussionsEnabled } } 0755232/b216/ --gdwarf2 (http block)
  • https://api.github.com/repos/astral-sh/setup-uv/git/ref/tags/eac588ad8def6316056a12d4907a9d4d84ff7a3b
    • Triggering command: /usr/bin/gh gh api /repos/astral-sh/setup-uv/git/ref/tags/eac588ad8def6316056a12d4907a9d4d84ff7a3b --jq .object.sha g/x/crypto/inter--log-target -I x_amd64/vet --gdwarf-5 0755232/b203/ lcache/go/1.25.0if [ -f .github/aw/actions-lock.json ]; then \ cp .github/aw/actions-lock.json pkg/workflow/data/action_pins.json; \ echo &#34;��� Action pins synced successfully/tmp/go-build3020726648/b295/importcfg ; \ fi @v1.�� 64/src/os/user 0755232/b216/ x_amd64/compile --gdwarf-5 --64 -o x_amd64/compile (http block)
  • https://api.github.com/repos/github/gh-aw
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw --jq .visibility ache/go/1.25.0/x64/src/net .cfg (http block)
  • https://api.github.com/repos/githubnext/agentics/git/ref/tags/
    • Triggering command: /usr/bin/gh gh api /repos/githubnext/agentics/git/ref/tags/# --jq .object.sha 64/src/os/user 0755232/b216/ x_amd64/vet --gdwarf-5 --64 -o x_amd64/vet 0755�� g_.a -I 64/pkg/tool/linux_amd64/vet --gdwarf-5 /viewport -o 64/pkg/tool/linu-f (http block)

If you need me to access, download, or install something from one of these locations, you can either:

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

💥 Automated smoke test review - all systems nominal!

Note

🔒 Integrity filtering filtered 2 items

Integrity filtering activated and filtered the following items during workflow execution.
This happens when a tool call accesses a resource that does not meet the required integrity or secrecy level of the workflow.

  • pr:#22048 (pull_request_read: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".)
  • pr:#22056 (pull_request_read: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".)

💥 [THE END] — Illustrated by Smoke Claude

@pelikhan pelikhan merged commit 78ef8b9 into main Mar 21, 2026
54 checks passed
@pelikhan pelikhan deleted the copilot/fix-smoke-codex-compilation branch March 21, 2026 01:09
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.

3 participants