Skip to content

fix(server): resolve upstream git ops against the clone root on subdir vault layouts - #78

Merged
sourcehawk merged 3 commits into
mainfrom
fix/upstream-subdir-vault-clone-root
Jul 31, 2026
Merged

fix(server): resolve upstream git ops against the clone root on subdir vault layouts#78
sourcehawk merged 3 commits into
mainfrom
fix/upstream-subdir-vault-clone-root

Conversation

@sourcehawk

@sourcehawk sourcehawk commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Description

On profiles where a vault's work-dir sits inside its clone rather than at the clone root, every upstream Sync button in the app is permanently disabled with "… is not a git checkout — sync needs a cloned repo". The handlers look for .git next to the content instead of at the clone root, so they conclude there's no clone at all. This affects the wiki, playbooks, and sessions surfaces, plus the wiki MCP preflight probe and the playbook-type PR flows.

Changes

  • Wiki, playbooks, and sessions upstream status endpoints now report gitCheckout: true on subdir layouts, re-enabling the Sync button and the commit / last-synced / remote-ahead hints in each header.
  • Wiki and playbooks sync endpoints no longer 424 on subdir layouts. The sessions sync handler already resolved the clone root; it now shares the same helper instead of inlining the fallback.
  • Session preflight no longer reports the wiki MCP as un-cloned.
  • The add-type and propose-type-removal PR flows no longer refuse with "local write only" / "propose-removal needs a cloned repo".
  • Playbook types now resolve their tracked flag correctly, so deleting an upstream-tracked type opens the propose-as-PR modal instead of offering a plain local delete that the next sync silently undoes.
  • When a vault genuinely has no clone, the error now names both the configured dir and where .git was expected, so the operator isn't pointed at a path they never set.
  • Unrelated to the fix but bundled to unblock CI: bumps the hoisted @emnapi/wasi-threads lockfile entry to 1.2.3. It's a dev+optional transitive dep on a floating range, and the stale 1.2.2 pin made npm ci reject the tree, failing the Frontend and E2E jobs before either ran a test.

Challenges

Not every git invocation wants the same working directory, so a blanket redirect to the clone root would have broken the callers it didn't fix. Commands that are repo-wide regardless of cwd (fetch, reset --hard, rev-parse) move to the clone root. Commands carrying a cwd-relative pathspec (git add <type>/type.txt) stay at the work-dir and only their .git gate moves. upstreamGitDir's doc comment states that split explicitly so a future call site doesn't collapse it.

The tracked probe was a third case with a second, independent fault. Rev paths like origin/HEAD:<type>/type.txt resolve from the repo root, not from cwd, so on a subdir layout it looked for investigation/type.txt when the file is at playbooks/investigation/type.txt — every type read as untracked. Prefixing the rev path with ./ makes it resolve relative to the work-dir, which is correct for both layouts and needs no subpath plumbing.

Testing

Seven tests in internal/server/upstream_clone_root_test.go, written failing first against a bare/clone pair with a vault subdir. They reproduced the exact production symptoms — a 424 carrying wiki dir … is not a git checkout — sync requires a cloned repo, gitCheckout: false on all three status endpoints, and tracked: false for a type whose type.txt is demonstrably at origin/HEAD — then went green. The ./ rev-path behaviour was confirmed against a real subdir clone before being relied on.

make test-go is clean race-wise and make lint reports 0 issues. The lockfile commit was verified by regenerating the entry in an isolated copy of package.json + package-lock.json, confirming npm ci accepts the result, and then running make test-frontend locally — 34 files, 225 tests pass. No frontend source is touched; the only frontend change is the one hoisted lockfile entry.

Reviewers on a flat-layout profile won't see any behaviour change; the helper falls through to the work-dir when no clone root is configured, and the existing flat-layout tests cover that path.

…r vault layouts

Subdir-layout profiles put each vault work-dir under the clone
(`<clone>/wikis`, `<clone>/playbooks`, `<clone>/sessions`) with `.git` at
the clone root. The upstream status and sync handlers stat
`<work-dir>/.git`, which never exists on that layout, so `gitCheckout`
comes back false and the frontend renders a permanently disabled Sync
button reading "… is not a git checkout". Options already carries the
clone root for all three vaults and the capability probes already walk
ancestry; only the sessions sync handler consulted it.

Route every upstream git probe through a shared `upstreamGitDir` helper.
Commands needing repo-wide scope (fetch, reset --hard, rev-parse) run at
the clone root; ones with cwd-relative pathspecs (`git add
<type>/type.txt`, the type-tracked probe) stay at the work-dir. The
tracked probe additionally gains a `./` rev prefix so
`origin/HEAD:./<type>/type.txt` resolves relative to the work-dir rather
than the repo root — without it every playbook type read as untracked on
a subdir layout, handing the operator the plain-delete UX for types the
next sync restores.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 31, 2026 13:35

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 upstream “not a git checkout” detection for subdir vault layouts (where the vault work-dir is inside the clone, but .git lives at the clone root), restoring upstream status/sync functionality across wiki, playbooks, and sessions in the launcher and in preflight.

Changes:

  • Introduces a shared upstreamGitDir(cloneRoot, workDir) helper and uses it to probe .git and run repo-wide git commands against the correct directory.
  • Updates wiki/playbooks/sessions upstream status + sync handlers (and playbook-type git checks) to respect clone-root vs work-dir semantics.
  • Adds focused regression tests covering subdir vault layouts, including playbook type tracked detection via origin/HEAD:./....

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.

Show a summary per file
File Description
internal/server/upstream_clone_root_test.go Adds regression tests for subdir vault clone-root git operations and playbook type tracked resolution.
internal/server/mcp_probe.go Fixes wiki MCP preflight probe to check .git at the clone root when configured.
internal/server/handlers_wiki_upstream.go Runs wiki upstream .git probes and repo-wide git commands against the clone root for subdir layouts.
internal/server/handlers_upstream.go Updates playbooks upstream handlers similarly and adds the shared upstreamGitDir helper.
internal/server/handlers_types.go Fixes playbook type listing/PR flows to detect git checkouts on subdir layouts and corrects tracked probing for subdir vault paths.
internal/server/handlers_sessions_upstream.go Aligns sessions upstream status/sync to use the shared clone-root resolver instead of inlined fallback logic.

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

… the lock

@emnapi/wasi-threads is a dev+optional transitive dep reached through a
floating range. The committed lock pinned the hoisted entry at 1.2.2
while the range now resolves to 1.2.3, so `npm ci` rejected the tree
outright and both the Frontend and E2E jobs failed before running a
single test. Regenerated the entry with `npm install --package-lock-only`
in an isolated copy; the diff is the one hoisted entry.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 31, 2026 13:59

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

Copilot reviewed 6 out of 7 changed files in this pull request and generated no new comments.

Files not reviewed (1)
  • frontend/package-lock.json: Generated file
Suppressed comments (3)

internal/server/handlers_upstream.go:105

  • upstreamGitDir’s doc comment says “git commands must run in” the returned directory, but the PR description (and callers like tryPushNewTypeAsPR) explicitly rely on some git operations running from the work-dir for pathspecs. This comment is misleading and could cause future call sites to move pathspec-based commands to the clone root and break subdir vault layouts.
// upstreamGitDir returns the dir git commands must run in for an upstream
// vault. Subdir-layout profiles put the vault work-dir under the clone
// (`<cloneRoot>/<subpath>`) with `.git` at the clone root, so probing the
// work-dir for `.git` misses. Flat-layout profiles leave cloneRoot empty,
// where the work-dir is the clone.

internal/server/mcp_probe.go:172

  • The probe error uses gitDir in the message, which can be the clone root on subdir layouts. That makes the error read as though the vault path is the clone root, and hides which configured vault path failed. Include both the configured vault path and where .git was expected so preflight output is actionable.
	gitDir := upstreamGitDir(cloneRoot, vaultPath)
	if _, err := os.Stat(filepath.Join(gitDir, ".git")); err != nil {
		return fmt.Errorf("wiki vault %s is not a git checkout", gitDir)
	}

frontend/package-lock.json:434

  • The PR description says “No frontend files are touched”, but this PR updates frontend/package-lock.json (bumping @emnapi/wasi-threads from 1.2.2 to 1.2.3). Either split this into a separate PR or update the PR description/testing notes to reflect the lockfile change and any intended impact on CI.

… in no-clone errors

upstreamGitDir's doc comment claimed the returned dir is where git
commands must run, but callers carrying a cwd-relative pathspec or rev
path (`git add <type>/type.txt`, the type-tracked probe) deliberately
stay at the work-dir. A future call site trusting that sentence would
move one of them to the clone root and reintroduce the subdir-layout
bug. State the real contract: where the repo is, not where git runs.

The no-clone errors had the mirror problem, reporting the probed clone
root — a path the operator never configured on a subdir layout. Route
them through a missingCheckoutDetail helper that names the configured
dir plus where `.git` was expected, and use it across the wiki,
playbooks, and sessions sync handlers and the wiki preflight probe so
all four read the same.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 31, 2026 14:16

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

Copilot reviewed 6 out of 7 changed files in this pull request and generated no new comments.

Files not reviewed (1)
  • frontend/package-lock.json: Generated file
Suppressed comments (2)

internal/server/handlers_types.go:352

  • This checkout precondition now probes .git at the clone root, but the HTTP error message still omits the configured dir/clone-root context. Using missingCheckoutDetail(...) here will match the improved errors in the upstream sync handlers and avoid pointing operators at a path they didn't configure.
	repoPath := a.opts.PluginPlaybooksDir
	if _, gerr := os.Stat(filepath.Join(upstreamGitDir(a.opts.PluginPlaybooksCloneRoot, repoPath), ".git")); gerr != nil {
		writeError(w, http.StatusPreconditionFailed, "playbooks dir is not a git checkout — propose-removal needs a cloned repo")
		return

internal/server/handlers_types.go:140

  • This .git gate now correctly probes the clone root, but the returned error string no longer includes the configured work-dir / clone-root detail (unlike the upstream sync/status handlers). Using missingCheckoutDetail(...) here will keep the operator-facing message actionable on subdir layouts.

This issue also appears on line 349 of the same file.

	// Git runs with cwd=repoPath so the relative `git add <type>/type.txt`
	// resolves, but on subdir layouts `.git` lives at the clone root.
	if _, err := os.Stat(filepath.Join(upstreamGitDir(a.opts.PluginPlaybooksCloneRoot, repoPath), ".git")); err != nil {
		return "", fmt.Errorf("playbooks dir is not a git checkout — local write only")
	}

@sourcehawk
sourcehawk merged commit 1bb70fe into main Jul 31, 2026
6 checks passed
@sourcehawk
sourcehawk deleted the fix/upstream-subdir-vault-clone-root branch July 31, 2026 14:45
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.

2 participants