Skip to content

fix: use pure Go filesystem traversal for git repo detection (fixes macOS ARM64 / Rosetta 2 failure) - #31050

Merged
pelikhan merged 9 commits into
mainfrom
copilot/fix-gh-aw-init-error
May 8, 2026
Merged

fix: use pure Go filesystem traversal for git repo detection (fixes macOS ARM64 / Rosetta 2 failure)#31050
pelikhan merged 9 commits into
mainfrom
copilot/fix-gh-aw-init-error

Conversation

CopilotAI commented May 8, 2026

Copy link
Copy Markdown
Contributor

gh aw init (and any command calling isGitRepo() / FindGitRoot()) fails with "not in a git repository" on macOS ARM64 when the x86_64 binary runs under Rosetta 2 — the exec.Command("git", "rev-parse", ...) subprocess call silently fails in that environment even when the user is inside a valid git repo.

Changes

  • pkg/gitutil/gitutil.go — Replace FindGitRoot() implementation: swap exec.Command("git", "rev-parse", "--show-toplevel") for os.Stat + filepath.Dir upward traversal. FindGitRootFrom(startDir string) now normalizes startDir to an absolute path via filepath.Abs, surfaces non-ErrNotExist stat errors (e.g. permission denied), and validates .git as either a directory (normal repo) or a regular file beginning with gitdir: (worktree / submodule pointer) — surfacing read errors instead of silently skipping an unreadable .git file.

  • pkg/cli/git.goisGitRepo() now delegates to gitutil.FindGitRoot() instead of its own exec.Command("git", "rev-parse", "--git-dir") call. findGitRootForPath() uses gitutil.FindGitRootFrom(dir).

  • pkg/gitutil/gitutil_test.goTestFindGitRootFrom extended to cover: start from repo root, start from nested subdir, start outside any repo, .git is a valid worktree marker file (detected from root and subdirectory), .git file without gitdir: prefix is not accepted, and relative path input (".") resolves correctly.

All actual git operations (commit, push, status, add, log) still invoke the git binary — only repository detection is changed.

// Before — fails silently under Rosetta 2funcisGitRepo() bool {
cmd:=exec.Command("git", "rev-parse", "--git-dir")
returncmd.Run() ==nil
}
// After — pure Go, no subprocessfuncisGitRepo() bool {
_, err:=gitutil.FindGitRoot()
returnerr==nil
}

[!WARNING]

CopilotAIand others added 3 commits May 8, 2026 15:57
Agent-Logs-Url: https://github.com/github/gh-aw/sessions/82628923-30ee-4061-af73-61b141aba2e4
Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
…rsal
Fixes `gh aw init` failing with "not in a git repository" on macOS ARM64
(Apple Silicon) when the x86_64 binary runs under Rosetta 2.
Previously, `isGitRepo()` and `FindGitRoot()` used `exec.Command("git", ...)`
to detect git repositories. Under Rosetta 2 (x86_64 emulation on ARM64), this
subprocess call can silently fail due to PATH differences or OS-level subprocess
restrictions.
Replace with pure Go filesystem traversal: walk up from the current directory
(or a given starting directory) looking for a `.git` entry (directory or file,
handling both normal repos and worktrees). This eliminates the dependency on
the `git` executable for repository detection, making it work regardless of
architecture, Rosetta translation, or git installation state.
Changes:
- `pkg/gitutil/gitutil.go`: `FindGitRoot()` now uses `os.Stat`/`filepath.Dir`
traversal; new exported `FindGitRootFrom(startDir)` helper for callers that
start from a specific directory
- `pkg/cli/git.go`: `isGitRepo()` delegates to `gitutil.FindGitRoot()`;
`findGitRootForPath()` uses `gitutil.FindGitRootFrom(dir)` instead of exec
- `pkg/gitutil/gitutil_test.go`: add `TestFindGitRootFrom` tests
- `pkg/gitutil/spec_test.go`: update doc comment to reflect new implementation
Agent-Logs-Url: https://github.com/github/gh-aw/sessions/82628923-30ee-4061-af73-61b141aba2e4
Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
…t robustness
Agent-Logs-Url: https://github.com/github/gh-aw/sessions/82628923-30ee-4061-af73-61b141aba2e4
Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
CopilotAI changed the title [WIP] Fix 'gh aw init' fails on macOS ARM64fix: use pure Go filesystem traversal for git repo detection (fixes macOS ARM64 / Rosetta 2 failure)May 8, 2026
CopilotAI requested a review from gh-aw-botMay 8, 2026 16:11
@pelikhan
pelikhan marked this pull request as ready for review May 8, 2026 17:10
CopilotAI review requested due to automatic review settings May 8, 2026 17:10

CopilotAI 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

Updates git repository detection to avoid invoking the git executable (addressing macOS ARM64 Rosetta 2 failures) by switching to filesystem-based traversal and centralizing the logic in pkg/gitutil.

Changes:

  • Replaced FindGitRoot() implementation to locate repo roots by walking up the directory tree looking for a .git entry.
  • Added FindGitRootFrom(startDir) and updated CLI git-repo detection helpers to use gitutil instead of subprocess calls.
  • Added tests for FindGitRootFrom and updated the public API spec text accordingly; lockfile workflows were also modified.
Show a summary per file
FileDescription
pkg/gitutil/gitutil.goImplements filesystem-based git root discovery and adds FindGitRootFrom.
pkg/cli/git.goSwitches repo detection/root lookup to gitutil.FindGitRoot* helpers.
pkg/gitutil/gitutil_test.goAdds test coverage for FindGitRootFrom.
pkg/gitutil/spec_test.goUpdates the spec wording for FindGitRoot to match new behavior.
.github/workflows/spec-librarian.lock.ymlUpdates pinned action version annotations/manifest metadata (now inconsistent in-file).
.github/workflows/copilot-pr-nlp-analysis.lock.ymlUpdates pinned action version annotations/manifest metadata (now inconsistent/duplicated in-file).

Copilot's findings

Tip

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

Comments suppressed due to low confidence (3)

.github/workflows/spec-librarian.lock.yml:470

  • This step is annotated as # v9 while other actions/github-script steps in the same lockfile are annotated # v9.0.0 (e.g., line 452). Please keep the version annotations consistent (ideally by regenerating the lockfile) to avoid confusion and drift.
 - name: Determine automatic lockdown mode for GitHub MCP Server
id: determine-automatic-lockdown
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9

.github/workflows/copilot-pr-nlp-analysis.lock.yml:53

  • The "Custom actions used" section now lists the same actions/github-script SHA twice with different versions (v9 and v9.0.0). This looks like an inconsistent regeneration and should be deduplicated/standardized so the lockfile accurately describes what is pinned.
# - actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0

.github/workflows/copilot-pr-nlp-analysis.lock.yml:522

  • This step is annotated as # v9 while other actions/github-script steps in the same lockfile are annotated # v9.0.0. Please keep version annotations consistent (ideally via lockfile regeneration) to avoid drift.
 - name: Determine automatic lockdown mode for GitHub MCP Server
id: determine-automatic-lockdown
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
  • Files reviewed: 6/6 changed files
  • Comments generated: 4

Comment on lines +297 to +301
func TestFindGitRootFrom(t *testing.T) {
t.Run("returns git root from the repository root itself", func(t *testing.T) {
gitRoot, err := FindGitRoot()
require.NoError(t, err, "must be inside a git repository")

@@ -1,5 +1,5 @@
# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"b21c866096e7bf37ad8390647a4e60737201a07a362b4a013fe73ebdfe63b049","strict":true,"agent_id":"copilot"}
# gh-aw-manifest: {"version":1,"secrets":["GH_AW_AGENT_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_ENDPOINT","GH_AW_OTEL_HEADERS","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.41","digest":"sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.41@sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41","digest":"sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41@sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.25.41","digest":"sha256:62171f2fa508667b8b0a9e096f826983f312e3da0ce894f80c0f83a875af60fe","pinned_image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.25.41@sha256:62171f2fa508667b8b0a9e096f826983f312e3da0ce894f80c0f83a875af60fe"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.41","digest":"sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.41@sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"ghcr.io/github/serena-mcp-server:latest","digest":"sha256:bf343399e3725c45528f531a230f3a04521d4cdef29f9a5af6282ff0d3c393c5","pinned_image":"ghcr.io/github/serena-mcp-server:latest@sha256:bf343399e3725c45528f531a230f3a04521d4cdef29f9a5af6282ff0d3c393c5"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]}
# gh-aw-manifest: {"version":1,"secrets":["GH_AW_AGENT_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_ENDPOINT","GH_AW_OTEL_HEADERS","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.41","digest":"sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.41@sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41","digest":"sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41@sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.25.41","digest":"sha256:62171f2fa508667b8b0a9e096f826983f312e3da0ce894f80c0f83a875af60fe","pinned_image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.25.41@sha256:62171f2fa508667b8b0a9e096f826983f312e3da0ce894f80c0f83a875af60fe"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.41","digest":"sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.41@sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"ghcr.io/github/serena-mcp-server:latest","digest":"sha256:bf343399e3725c45528f531a230f3a04521d4cdef29f9a5af6282ff0d3c393c5","pinned_image":"ghcr.io/github/serena-mcp-server:latest@sha256:bf343399e3725c45528f531a230f3a04521d4cdef29f9a5af6282ff0d3c393c5"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]}
@@ -1,5 +1,5 @@
# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"e680d07d85ca99dc6dc3ee4be59cdb3c9d1c49e7bf28efa85f1641c408445d8c","strict":true,"agent_id":"copilot"}
# gh-aw-manifest: {"version":1,"secrets":["GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_ENDPOINT","GH_AW_OTEL_HEADERS","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/setup-python","sha":"a309ff8b426b58ec0e2a45f0f869d46889d02405","version":"v6.2.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.41","digest":"sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.41@sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41","digest":"sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41@sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.41","digest":"sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.41@sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]}
# gh-aw-manifest: {"version":1,"secrets":["GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_ENDPOINT","GH_AW_OTEL_HEADERS","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/setup-python","sha":"a309ff8b426b58ec0e2a45f0f869d46889d02405","version":"v6.2.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.41","digest":"sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.41@sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41","digest":"sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41@sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.41","digest":"sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.41@sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]}
Comment on lines +107 to +113
func FindGitRootFrom(startDir string) (string, error) {
dir := startDir
for {
if _, err := os.Stat(filepath.Join(dir, ".git")); err == nil {
return dir, nil
}
parent := filepath.Dir(dir)
@github-actions

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 90/100

Excellent test quality

MetricValue
New/modified tests analyzed3 top-level functions (7 subtests)
✅ Design tests (behavioral contracts)3 (100%)
⚠️ Implementation tests (low value)0 (0%)
Tests with error/edge cases2 of 3 (67%)
Duplicate test clusters0
Test inflation detectedNo (ratio 1.06×)
🚨 Coding-guideline violationsNone

Test Classification Details

The PR modifies pkg/gitutil/gitutil_test.go (+38 lines) and pkg/gitutil/spec_test.go (+3/-2 lines). The core fix replaces git rev-parse subprocess calls with pure Go .git directory traversal; the new tests validate that new behaviour.

View per-test classification table
TestFileClassificationIssues Detected
TestFindGitRootpkg/gitutil/gitutil_test.go✅ DesignHappy-path only; error path delegated to TestFindGitRootFrom
TestFindGitRootFrom (from root)pkg/gitutil/gitutil_test.go✅ DesignVerifies idempotency: starting at root returns root
TestFindGitRootFrom (from subdir)pkg/gitutil/gitutil_test.go✅ DesignVerifies upward traversal from a temp subdir
TestFindGitRootFrom (error – non-repo dir)pkg/gitutil/gitutil_test.go✅ DesignEdge case: explicit error path + error message assertion
TestReadFileFromHEADWithRoot (reads file)pkg/gitutil/gitutil_test.go✅ DesignVerifies correct content returned from HEAD
TestReadFileFromHEADWithRoot (path outside root)pkg/gitutil/gitutil_test.go✅ DesignEdge case: path-traversal rejection
TestReadFileFromHEADWithRoot (empty gitRoot)pkg/gitutil/gitutil_test.go✅ DesignEdge case: empty-string input guard

Flagged Tests — Requires Review

i️ TestFindGitRoot — happy-path only

Classification: Design test (high value), but missing an explicit error path.

Issue: FindGitRoot() is a thin wrapper around FindGitRootFrom(os.Getwd()). Its own error path ("not in a git repo") is tested indirectly via TestFindGitRootFrom. This is acceptable given the function's simplicity, but a direct error test (e.g., changing the working directory to t.TempDir() before calling) would make the contract self-contained.

Impact if deleted: Low — TestFindGitRootFrom already covers the error branch. Deleting TestFindGitRoot would leave the wrapper itself untested.

Suggested improvement (non-blocking): Add a subtest that changes os.Chdir to a temp directory with no .git ancestor and asserts the returned error, or explicitly document that the error path is covered by TestFindGitRootFrom.


Score Breakdown

ComponentPointsNotes
Behavioral Coverage (40 pts)403/3 tests are design tests (100%)
Error/Edge Case Coverage (30 pts)202/3 top-level functions explicitly test error paths
Low Duplication (20 pts)20No copy-paste clusters detected
Proportional Growth (10 pts)10Test:prod ratio = 1.06 (well below 2:1 threshold)

Language Support

Tests analyzed:

  • 🐹 Go (*_test.go): 7 subtests across 3 functions — unit (//go:build !integration) ✅
  • 🟨 JavaScript (*.test.cjs, *.test.js): 0 tests changed in this PR

Verdict

Check passed. 0% of new tests are implementation tests (threshold: 30%). Build tags are present on all new/modified test files. No mock libraries detected. All tests exercise observable behavioral contracts of the new pure-Go git root detection.


📖 Understanding Test Classifications

Design Tests (High Value) verify what the system does:

  • Assert on observable outputs, return values, or state changes
  • Cover error paths and boundary conditions
  • Would catch a behavioral regression if deleted
  • Remain valid even after internal refactoring

Implementation Tests (Low Value) verify how the system does it:

  • Assert on internal function calls (mocking internals)
  • Only test the happy path with typical inputs
  • Break during legitimate refactoring even when behavior is correct
  • Give false assurance: they pass even when the system is wrong

Goal: Shift toward tests that describe the system's behavioral contract — the promises it makes to its users and collaborators.

References:§25568904322

🧪 Test quality analysis by Test Quality Sentinel · ● 7.3M ·

@github-actionsgithub-actionsBot 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 excellent — 0% of new tests are implementation tests (threshold: 30%). All 7 new subtests exercise behavioral contracts of the new pure-Go git root detection. Build tags present, no mock libraries used.

@pelikhan

Copy link
Copy Markdown
Collaborator

@copilot merge main and recompile

CopilotAIand others added 2 commits May 8, 2026 19:17
…t-error
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

CopilotAI commented May 8, 2026

Copy link
Copy Markdown
ContributorAuthor

@copilot merge main and recompile

Done — merged latest main (8 commits) and ran make build && make recompile. All 217 workflow lock files were regenerated and tests pass.

CopilotAI requested a review from pelikhanMay 8, 2026 19:20
@pelikhan

Copy link
Copy Markdown
Collaborator

@copilot add integration tests for git repos and worktrees

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

CopilotAI commented May 8, 2026

Copy link
Copy Markdown
ContributorAuthor

@copilot add integration tests for git repos and worktrees

Done in the latest commit. Added four new test cases to TestFindGitRootFrom:

  • Worktree marker file.git is a regular file starting with gitdir:, detected from both the root and a subdirectory
  • Invalid .git file — a .git file without the gitdir: prefix is not treated as a valid repo root
  • Relative path inputFindGitRootFrom(".") resolves correctly via filepath.Abs

Also fixed FindGitRootFrom itself: it now normalizes startDir to an absolute path up front, surfaces non-ErrNotExist stat errors, and propagates os.ReadFile errors instead of silently continuing past an unreadable .git file.

@pelikhan
pelikhan merged commit b77112c into mainMay 8, 2026
18 checks passed
@pelikhan
pelikhan deleted the copilot/fix-gh-aw-init-error branch May 8, 2026 19:40
Sign up for freeto 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.

gh aw init fails with "not in a git repository" on macOS ARM64 (Apple Silicon)

4 participants

@pelikhan@gh-aw-bot