Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ category: "bug"
source: "agent-finding"
found_during: "clean-slate-sweep"
found_at: "internal/core/core.go"
resolution: "core.Status now tests .git existence (file or dir) not dir-ness, so a linked worktree/submodule (where .git is a gitfile) reports IsGitRepo=true. os.Stat is correct (rejects dangling symlinks). ruthless SHIP; scoped to IsGitRepo only. PR opened."
---

core.Status.IsGitRepo=false in a linked git worktree or submodule: isDir(.git) requires a directory but .git is a regular gitfile (gitlink) in worktrees/submodules, so a genuine checkout reports not-a-git-repo (core.go:47, C1). Detector: an exists-not-isDir check for .git with a worktree fixture. Corpus: C1.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,11 @@ called out in a **Breaking** section.
- The `created` and `updated` frontmatter fields on issues. Git is the canonical
source of an issue's timeline; the ledger no longer duplicates it.

### Fixed

- `abcd` status now reports `IsGitRepo` correctly in a linked git worktree or a
submodule, where `.git` is a regular gitfile rather than a directory (iss-72).

### Security

- **Release receipt-gate hardening** (iss-70). The `receipt_gate` record-lint
Expand Down
15 changes: 13 additions & 2 deletions internal/core/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,11 @@ func Status(dir string) (StatusInfo, error) {
return StatusInfo{}, err
}
s := StatusInfo{
Dir: abs,
IsGitRepo: isDir(filepath.Join(abs, ".git")),
Dir: abs,
// .git is a directory in a normal clone but a regular gitfile in a linked
// worktree or submodule — both are genuine checkouts, so test existence, not
// dir-ness. HasRecord/WorkTiers stay dir-only (those must be directories).
IsGitRepo: exists(filepath.Join(abs, ".git")),
HasRecord: isDir(filepath.Join(abs, ".abcd", "development")),
}
for _, tier := range []struct{ path, name string }{
Expand All @@ -63,3 +66,11 @@ func isDir(p string) bool {
fi, err := os.Stat(p)
return err == nil && fi.IsDir()
}

// exists reports whether p exists (a file or a directory). A .git gitfile in a
// worktree/submodule is a regular file, so a plain existence check is the correct
// "is this a git checkout" test.
func exists(p string) bool {
_, err := os.Stat(p)
return err == nil
}
17 changes: 17 additions & 0 deletions internal/core/core_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,23 @@ func TestStatusBareDir(t *testing.T) {
}
}

// TestStatusGitfileWorktree (iss-72) proves a linked worktree or submodule —
// where .git is a regular gitfile, not a directory — is still reported as a git
// repo. isDir would report false for a genuine checkout.
func TestStatusGitfileWorktree(t *testing.T) {
dir := t.TempDir()
if err := os.WriteFile(filepath.Join(dir, ".git"), []byte("gitdir: /somewhere/.git/worktrees/wt\n"), 0o644); err != nil {
t.Fatal(err)
}
s, err := Status(dir)
if err != nil {
t.Fatal(err)
}
if !s.IsGitRepo {
t.Fatal("a worktree/submodule .git gitfile must report IsGitRepo=true")
}
}

func TestStatusWithRecordAndGit(t *testing.T) {
dir := t.TempDir()
mustMkdir(t, filepath.Join(dir, ".git"))
Expand Down
Loading