Skip to content

fix: workspace-relative read paths for view tool + awk in go-source-analysis#39958

Merged
pelikhan merged 4 commits into
mainfrom
copilot/daily-compiler-quality-check
Jun 18, 2026
Merged

fix: workspace-relative read paths for view tool + awk in go-source-analysis#39958
pelikhan merged 4 commits into
mainfrom
copilot/daily-compiler-quality-check

Conversation

Copilot AI commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

The daily-compiler-quality workflow hit the tool denial limit (5/5) because the Copilot SDK's view tool sends absolute paths to the permission handler, but go-source-analysis.md only grants shell(cat pkg/**/*.go) — a workspace-relative pattern. isReadPathAllowedByShellRules had no workspace context, so every view() call was denied (3 denials), then the agent fell back to python3 heredocs which are also blocked (2 more → session stopped).

Permission handler fix

  • isReadPathAllowedByShellRules — new optional workspaceRoot parameter; when the requested path is absolute and the pattern is relative, strips the workspace prefix and retries the glob match
  • buildCopilotSDKPermissionHandlerlogOptions extended with workspaceRoot?: string
  • copilot_sdk_session.cjs — passes process.env.GITHUB_WORKSPACE as workspaceRoot
// shell(cat pkg/**/*.go) now permits both:
view("pkg/workflow/compiler.go")                               // relative — was already allowed
view("/home/runner/work/gh-aw/gh-aw/pkg/workflow/compiler.go") // absolute — was denied, now allowed
// paths outside the workspace root remain denied
view("/other/workspace/pkg/workflow/file.go")                  // still rejected

Shared workflow fix

  • go-source-analysis.md — adds awk to the bash tool allowlist as a capable non-Python alternative for text analysis (function-length counting, etc.), preventing fallback to python3 heredocs
  • Recompiled all 4 workflows that import go-source-analysis.md (daily-compiler-quality, duplicate-code-detector, semantic-function-refactor, spec-extractor)

Copilot AI and others added 2 commits June 18, 2026 05:39
…lysis

The daily-compiler-quality workflow was hitting the tool denial limit (5/5)
because the Copilot SDK's `view` tool delivers file paths as absolute paths
(e.g. /home/runner/work/gh-aw/gh-aw/pkg/workflow/file.go) but the
go-source-analysis shared import only grants shell(cat pkg/**/*.go) — a
workspace-relative glob.

The isReadPathAllowedByShellRules permission function did not know about the
workspace root, so it could not strip the GITHUB_WORKSPACE prefix before
matching against the relative pattern, causing every view() call to be
denied (3 denials). The agent then fell back to Python heredocs which are
also denied (2 more denials → limit hit).

Fixes:
1. copilot_sdk_permissions.cjs: Add optional workspaceRoot parameter to
   isReadPathAllowedByShellRules. When the requested path is absolute and
   the pattern is relative, try matching the path after stripping the
   workspace prefix.
2. buildCopilotSDKPermissionHandler: Accept workspaceRoot in options and
   thread it through to the read permission check.
3. copilot_sdk_session.cjs: Pass process.env.GITHUB_WORKSPACE as
   workspaceRoot when building the permission handler.
4. go-source-analysis.md: Add awk to bash tools as a capable non-Python
   alternative for complex text analysis (e.g. function-length counting).
5. Recompile daily-compiler-quality, duplicate-code-detector,
   semantic-function-refactor, and spec-extractor (all import
   go-source-analysis.md).

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix daily compiler quality check tool denial issue fix: workspace-relative read paths for view tool + awk in go-source-analysis Jun 18, 2026
Copilot AI requested a review from pelikhan June 18, 2026 05:46
@pelikhan pelikhan marked this pull request as ready for review June 18, 2026 05:47
Copilot AI review requested due to automatic review settings June 18, 2026 05:47

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Fixes Copilot SDK permission handling so read/view() requests delivered as absolute paths can still be approved by workflows that grant only workspace-relative shell globs (e.g. shell(cat pkg/**/*.go)), and updates the shared Go analysis workflow allowlist to include awk to avoid blocked Python fallbacks.

Changes:

  • Extend isReadPathAllowedByShellRules to optionally match absolute paths against relative patterns by stripping a configured workspace root.
  • Plumb GITHUB_WORKSPACE through the Copilot SDK session so the permission handler has workspace context, and add a regression test for the absolute-path case.
  • Add awk to .github/workflows/shared/go-source-analysis.md and recompile dependent workflows’ .lock.yml outputs.
Show a summary per file
File Description
actions/setup/js/copilot_sdk_session.cjs Passes GITHUB_WORKSPACE to the permission handler as workspaceRoot.
actions/setup/js/copilot_sdk_permissions.cjs Adds workspace-aware fallback matching for absolute read paths vs relative shell patterns.
actions/setup/js/copilot_sdk_driver.test.cjs Adds a test covering approval of absolute workspace paths under a relative cat pkg/**/*.go shell rule.
.github/workflows/shared/go-source-analysis.md Adds awk to the shared bash tool allowlist for Go source analysis workflows.
.github/workflows/daily-compiler-quality.lock.yml Recompiled workflow output to include the updated allowlist (notably shell(awk)).
.github/workflows/duplicate-code-detector.lock.yml Recompiled workflow output to reflect shared workflow changes.
.github/workflows/semantic-function-refactor.lock.yml Recompiled workflow output to include the updated allowlist (notably Bash(awk)).
.github/workflows/spec-extractor.lock.yml Recompiled workflow output to include the updated allowlist (notably shell(awk)).

Copilot's findings

Tip

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

  • Files reviewed: 8/8 changed files
  • Comments generated: 2

Comment on lines +181 to +187
let relativeRequestedPath;
if (workspaceRoot && normalizedRequestedPath.startsWith("/")) {
const normalizedWorkspace = normalizePermissionPath(workspaceRoot);
if (normalizedRequestedPath.startsWith(normalizedWorkspace + "/")) {
relativeRequestedPath = normalizedRequestedPath.slice(normalizedWorkspace.length + 1);
}
}
Comment on lines 12 to +15
- head -n * pkg/**/*.go
- grep -r "func " pkg --include="*.go"
- cat pkg/**/*.go
- awk
@github-actions

github-actions Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

@github-actions

github-actions Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

No ADR enforcement needed for PR #39958: PR does not have the 'implementation' label and has 0 new lines of code in business logic directories (≤100 threshold). Neither Condition A nor Condition B is met.

@github-actions

github-actions Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

@github-actions

github-actions Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

@github-actions github-actions Bot mentioned this pull request Jun 18, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Skills-Based Review 🧠

Applied /diagnose and /tdd — commenting with improvement suggestions, no blocking issues.

📋 Key Themes & Highlights

Key Themes

  • Path traversal gap: normalizePermissionPath does not resolve .. components before glob-matching the workspace-relative path. The .go extension in the current pkg/**/*.go pattern guards against exploitation today, but this is latent risk for future patterns without extension constraints.
  • Test coverage scope: Integration test in copilot_sdk_driver.test.cjs is thorough for the main allow/deny matrix, but there is no dedicated unit test file for copilot_sdk_permissions.cjs, and the GITHUB_WORKSPACE unset case is not explicitly verified.
  • logOptions semantic creep: workspaceRoot is execution context, not a logging option — minor but worth addressing for long-term clarity.
  • awk allowlist breadth: Unlike every other bash entry in go-source-analysis.md, awk carries no path restriction.

Positive Highlights

  • ✅ Root cause correctly identified and surgically fixed — the abstraction mismatch between SDK absolute paths and workflow-relative patterns is cleanly resolved
  • ✅ Security boundary is tight: the workspace-prefix check uses startsWith(normalizedWorkspace + "/") so /home/runner/work/gh-aw/gh-aw-evil/pkg/foo.go is correctly rejected
  • ✅ New integration test explicitly validates the /other/workspace/pkg/ and /etc/passwd denial cases — good security-first test authorship
  • awk addition has a clear operational motivation (breaking the python3 heredoc fallback chain) and is documented in the PR body
  • ✅ Four recompiled lock files are a natural consequence of the shared workflow change; no independent review needed

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer

if (workspaceRoot && normalizedRequestedPath.startsWith("/")) {
const normalizedWorkspace = normalizePermissionPath(workspaceRoot);
if (normalizedRequestedPath.startsWith(normalizedWorkspace + "/")) {
relativeRequestedPath = normalizedRequestedPath.slice(normalizedWorkspace.length + 1);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/diagnose] normalizePermissionPath only strips trailing slashes and converts backslashes — it does not resolve .. components. A crafted absolute path like /home/runner/work/gh-aw/gh-aw/pkg/../../AGENTS.md produces relativeRequestedPath = "pkg/../../AGENTS.md", which path.posix.matchesGlob could match against pkg/**/* patterns that lack an extension constraint. For the current pkg/**/*.go the .go requirement saves us, but future unrestricted patterns would be vulnerable.

💡 Suggested hardening

Normalise the relative path before glob-matching:

if (relativeRequestedPath !== undefined) {
  relativeRequestedPath = path.posix.normalize(relativeRequestedPath);
  if (relativeRequestedPath.startsWith("..") || relativeRequestedPath.startsWith("/")) {
    relativeRequestedPath = undefined; // escaping path — discard
  }
}

And add a regression test:

expect(onPermissionRequest({ kind: "read", path: "/home/runner/work/gh-aw/gh-aw/pkg/../../AGENTS.md", intention: "" }))
  .toEqual({ kind: "reject", feedback: "Tool invocation is not allowed by workflow tool permissions." });

* @returns {boolean}
*/
function isReadPathAllowedByShellRules(requestedPath, allowedPathPatterns) {
function isReadPathAllowedByShellRules(requestedPath, allowedPathPatterns, workspaceRoot) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/tdd] The core logic change in isReadPathAllowedByShellRules is only exercised via the full driver integration test — there is no copilot_sdk_permissions.test.cjs. Dedicated unit tests for this function would be faster to run, easier to extend, and would let you test workspaceRoot edge cases (trailing slash on root, workspaceRoot undefined, empty string) without spinning up an SDK mock.

💡 Example unit test structure
// copilot_sdk_permissions.test.cjs (new file)
const { isReadPathAllowedByShellRules } = require('./copilot_sdk_permissions.cjs');

describe('isReadPathAllowedByShellRules', () => {
  it('allows absolute path within workspace via relative pattern', () => {
    expect(isReadPathAllowedByShellRules(
      '/home/runner/work/repo/pkg/foo.go',
      ['pkg/**/*.go'],
      '/home/runner/work/repo'
    )).toBe(true);
  });

  it('denies path outside workspace even if it contains matching segment', () => {
    expect(isReadPathAllowedByShellRules(
      '/other/workspace/pkg/foo.go',
      ['pkg/**/*.go'],
      '/home/runner/work/repo'
    )).toBe(false);
  });

  it('behaves correctly when workspaceRoot is undefined', () => {
    expect(isReadPathAllowedByShellRules(
      '/home/runner/work/repo/pkg/foo.go',
      ['pkg/**/*.go'],
      undefined
    )).toBe(false);
  });
});

* @param {CopilotSDKPermissionConfig | undefined} permissionConfig
* @param {import("@github/copilot-sdk").PermissionHandler} approveAll
* @param {{coreLogger?: CopilotSDKCoreLogger, logger?: (msg: string) => void, onDenied?: (requestSummary: string) => void}=} logOptions
* @param {{coreLogger?: CopilotSDKCoreLogger, logger?: (msg: string) => void, onDenied?: (requestSummary: string) => void, workspaceRoot?: string}=} logOptions

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/zoom-out] workspaceRoot is a runtime execution-context value, not a logging option. Bundling it into logOptions works, but the type name creates a false impression that this object is only about diagnostics. A future reader adding a new logOptions field might not think to look here for execution-critical inputs.

💡 Alternative: use a dedicated options bag or extend permissionConfig

Option A — add a second options parameter (minimal churn):

function buildCopilotSDKPermissionHandler(permissionConfig, approveAll, logOptions, runtimeOptions) {
  // runtimeOptions: { workspaceRoot?: string }
}

Option B — extend permissionConfig (already the right shape for execution context):

// CopilotSDKPermissionConfig
{
  allowAllTools?: boolean;
  allowedTools?: string[];
  workspaceRoot?: string;  // absolute path used to relativise SDK-emitted absolute paths
}

Either approach makes the intent of the parameter clearer at the call site.

- head -n * pkg/**/*.go
- grep -r "func " pkg --include="*.go"
- cat pkg/**/*.go
- awk

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/zoom-out] Every other bash entry in this file scopes its path — cat pkg/**/*.go, wc -l pkg/**/*.go, grep -r "func " pkg .... The bare awk entry grants the command with no argument restriction, so an agent could invoke awk '{print}' /etc/shadow and the shell permission would be approved (file-read permission is a separate gate, but defence-in-depth argues for scoping here too).

💡 Suggestion: scope awk to pkg files
- awk '{...}' pkg/**/*.go   # or the most common expected form

If the expected use is function-length counting across all Go files, awk -F '' '{...}' pkg/**/*.go would be more precise and consistent with the other entries.

});
});

it("allows read requests when absolute path matches a workspace-relative shell pattern", async () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/tdd] The new test only exercises the path where GITHUB_WORKSPACE is set. There's no counterpart case where workspaceRoot is undefined (i.e., GITHUB_WORKSPACE unset at session creation time) — this would verify that the pre-existing absolute-path denial still works unchanged, and that the new code doesn't accidentally widen permissions when the env var is absent.

💡 Suggested additional case
it("denies absolute read paths when GITHUB_WORKSPACE is not set", async () => {
  const prevWorkspace = process.env.GITHUB_WORKSPACE;
  delete process.env.GITHUB_WORKSPACE;
  try {
    // ... same mock setup ...
    // Absolute path must be denied because no workspaceRoot is available.
    expect(onPermissionRequest({ kind: "read", path: "/home/runner/work/gh-aw/gh-aw/pkg/workflow/compiler.go", intention: "" }))
      .toEqual({ kind: "reject", feedback: "Tool invocation is not allowed by workflow tool permissions." });
  } finally {
    if (prevWorkspace === undefined) delete process.env.GITHUB_WORKSPACE;
    else process.env.GITHUB_WORKSPACE = prevWorkspace;
  }
});

@github-actions

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 90/100 — Excellent

Analyzed 1 test (1 design, 0 implementation, 0 guideline violations) in actions/setup/js/copilot_sdk_driver.test.cjs.

📊 Metrics & Test Classification (1 test analyzed)
Metric Value
New/modified tests analyzed 1
✅ Design tests (behavioral contracts) 1 (100%)
⚠️ Implementation tests (low value) 0 (0%)
Tests with error/edge cases 1 (100%)
Duplicate test clusters 0
Test inflation detected Borderline — 69 test lines / 33 production lines ≈ 2.09:1 (threshold 2:1; see note)
🚨 Coding-guideline violations 0
Test File Classification Issues Detected
allows read requests when absolute path matches a workspace-relative shell pattern actions/setup/js/copilot_sdk_driver.test.cjs:401 ✅ Design None — see minor gaps below

Go: 0 (*_test.go); JavaScript: 1 (copilot_sdk_driver.test.cjs). Other languages detected but not scored.

Test inflation note: The test file copilot_sdk_driver.test.cjs added 69 lines; however the directly corresponding production file (copilot_sdk_driver.cjs) was not changed. The production logic under test lives in copilot_sdk_permissions.cjs (+33 lines), which is a dependency called through the driver. Comparing against that file gives a 2.09:1 ratio — marginally over threshold. Given the architectural intent (testing the driver→permissions integration path), 10 points are deducted but no hard failure is triggered.

📋 Test Detail — allows read requests when absolute path matches a workspace-relative shell pattern

What it covers (7 assertions):

# Path Expected
1–3 Absolute workspace-rooted .go files under pkg/workflow/ approve-once
4 Relative pkg/workflow/compiler.go approve-once (backwards-compat)
5 /.../gh-aw/AGENTS.md (in workspace, outside pkg/**) reject
6 /etc/passwd (outside workspace entirely) reject
7 /other/workspace/pkg/workflow/file.go (contains /pkg/ but wrong root) reject ⭐ adversarial edge case

Mocking: All vi.fn() calls stub external SDK lifecycle methods (createSession, sendAndWait, disconnect, stop, start, RuntimeConnection.forUri) — these are legitimate infrastructure stubs for an external SDK, not internal business-logic mocks.

Minor gaps (non-blocking):

  • disconnect and stop are stubbed but never asserted as called — a lifecycle-cleanup regression would pass silently
  • RuntimeConnection.forUri is never verified to receive the correct sdkUri
  • sendAndWait call arguments are not asserted — the test only reads the permission callback extracted from createSession, so it would pass even if the agent loop never sent the prompt

Verdict

Check passed. 0% implementation tests (threshold: 30%). The single new test enforces a behavioral contract — that the workspace-relative path expansion logic correctly approves in-scope absolute paths and rejects out-of-scope ones, including an adversarial case where a foreign-workspace path contains the permitted pattern substring.

🧪 Test quality analysis by Test Quality Sentinel ·

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ Test Quality Sentinel: 90/100. Test quality is acceptable — 0% of new tests are implementation tests (threshold: 30%).

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The core fix in isReadPathAllowedByShellRules is correct and well-reasoned: pre-computing relativeRequestedPath once outside the some() loop, guarding with workspaceRoot && startsWith("/") before the prefix check, and the !normalizedPattern.startsWith("/") guard on the fallback are all the right calls. The test covers the exact failure scenario from the bug report and correctly rejects out-of-workspace and non-matching paths.

Two non-blocking concerns are flagged inline:

### Summary of findings

Medium — Lexical workspace containment enables symlink escape (new attack surface)

isReadPathAllowedByShellRules line ~184: the new startsWith(normalizedWorkspace + "/") check is purely textual. A committed symlink at pkg/leaked.go → /host/secrets/file.go passes the containment check and matchesGlob approves it, causing the SDK to read the symlink target outside the workspace. This attack path did not exist before this PR because absolute paths were previously always denied against relative patterns. Practical risk is low (requires repo-level compromise to plant the symlink), but this is a security boundary and symlink hygiene is fragile. See inline comment for a realpathSync-based fix.

Medium — awk breaks go-source-analysis.md's scoped-only convention

go-source-analysis.md line 15: every prior tool entry in this shared component is path-scoped to pkg/. Adding bare awk (no path restriction) breaks that principle and gives all 4 importing workflows the ability to run awk against any sandbox-accessible file or invoke shells via system(). The practical impact is limited because the compiled lock files already include unrestricted shell(cat) from other shared components, but the contract of this shared file has widened silently.

🔎 Code quality review by PR Code Quality Reviewer

- head -n * pkg/**/*.go
- grep -r "func " pkg --include="*.go"
- cat pkg/**/*.go
- awk

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

awk breaks the scoped-only convention of this shared component: every other tool listed here is path-restricted to pkg/; bare awk is unrestricted and can read any sandbox-accessible file or invoke shell commands via system().

💡 Analysis and suggestion

All prior entries in this shared workflow are scoped:

- wc -l pkg/**/*.go
- head -n * pkg/**/*.go
- grep -r "func " pkg --include="*.go"
- cat pkg/**/*.go

GNU awk is fundamentally different — it can read arbitrary files (awk '{print}' /tmp/gh-aw/cache-memory/file) and execute shell commands (awk 'BEGIN{system("env")}' /dev/null). The compiled lock files happen to include unrestricted shell(cat) from other shared components, so the practical containment boundary does not change. But this shared component silently widens its contract for all 4 importing workflows without documentation.

Preferred alternative if awk is only needed for Go source analysis:

  - awk pkg/**/*.go

That pattern is consistent with the rest of the list and is the form extractReadablePathPatternsFromShellRule would recognise as path-scoped (though that function currently only handles cat, xargs+cat, and ls prefixes, so an awk path restriction would not gate the SDK view permission handler either way).

let relativeRequestedPath;
if (workspaceRoot && normalizedRequestedPath.startsWith("/")) {
const normalizedWorkspace = normalizePermissionPath(workspaceRoot);
if (normalizedRequestedPath.startsWith(normalizedWorkspace + "/")) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Workspace-prefix containment is purely lexical — symlinks under pkg/ can escape the workspace boundary: the startsWith(normalizedWorkspace + "/") check does not resolve symlinks, so a symlinked path like /workspace/pkg/leaked.go → /secrets/leaked.go passes the containment check and matchesGlob("pkg/leaked.go", "pkg/**/*.go") approves it, ultimately reading the symlink target outside the workspace.

💡 Details and suggested mitigation

Attack path (newly enabled by this PR):

  1. Workspace contains pkg/leaked.go → symlink → /host/secrets/leaked.go
  2. Agent calls view("/workspace/pkg/leaked.go")
  3. normalizedRequestedPath.startsWith("/workspace/") → ✅
  4. relativeRequestedPath = "pkg/leaked.go"
  5. matchesGlob("pkg/leaked.go", "pkg/**/*.go") → ✅ (approved)
  6. OS follows symlink → reads /host/secrets/leaked.go

Before this PR, step 3 was the first check and absolute paths would fail to match relative patterns, so this attack was not possible via the view tool.

Practical risk level: Low-to-medium. An adversarial symlink would need to be committed to the repo (or injected into the runner checkout), which requires compromise of the repo itself. But the permission handler is a security boundary and relying on upstream symlink hygiene is fragile.

Suggested fix: Resolve symlinks before the workspace containment check:

const fs = require("fs");
// ...
let realRequestedPath = normalizedRequestedPath;
try {
  realRequestedPath = normalizePermissionPath(fs.realpathSync(requestedPath));
} catch {
  // file may not exist yet; fall through to lexical check
}
let realWorkspace;
try {
  realWorkspace = normalizePermissionPath(fs.realpathSync(workspaceRoot));
} catch {
  realWorkspace = normalizedWorkspace;
}
if (realRequestedPath.startsWith(realWorkspace + "/")) {
  relativeRequestedPath = realRequestedPath.slice(realWorkspace.length + 1);
}

Alternatively, document that symlinks are intentionally out-of-scope for this permission model.

@pelikhan pelikhan merged commit 669eab3 into main Jun 18, 2026
85 checks passed
@pelikhan pelikhan deleted the copilot/daily-compiler-quality-check branch June 18, 2026 06:25
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