gate-worktree-git-safety

SkillDev tools

Use when changing recursive gate containment, run worktree placement, bare-gate Git calls, GitHub PR targeting, or post-receive hooks.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the gate-worktree-git-safety skill

What this skill tells your AI

The instructions your AI receives, as published by kunchenguid/no-mistakes in .agents/skills/gate-worktree-git-safety/SKILL.md and read by ahel’s review.

Recursive Gate-Execution Containment

  • internal/gatecontext is the single classifier for recursive pipeline control. It combines canonical registered gate common-directory identity with OS-authenticated IPC peer ancestry; NO_MISTAKES_GATE is diagnostic only. CLI preflight, daemon mutation ingress, gate init/eject, branch-sync mutation, and the managed pre-receive hook must all keep using that owner so marker removal, cwd changes, and direct pushes cannot bypass refusal. Read-only AXI status/logs, help, and doctor remain available. Regressions: internal/gatecontext, TestGateStepCannotStartRecursivePipeline.
  • Every pipeline agent prompt receives the phase boundary from internal/gateguidance, and the generated user-level skill reuses the same owner. Step agents return only their assigned phase; the outer executor alone controls other validation, push, PR, and CI phases. Edit internal/skill/skill.go, then run make skill; never edit the generated skill directly.

Filesystem and Paths

  • Use filepath.Join; respect NM_HOME for app state; directories are 0o755 and files 0o644 by convention.
  • On macOS, path comparisons may need symlink resolution (/var vs /private/var); use worktrees.Canonical/worktrees.Contains wherever run worktree paths are compared, so one spelling matches everywhere.
  • Run worktree placement (worktree_roots) is owned by internal/worktrees. Configuration decides it exactly once, at run creation (Layout.Dir in RunManager.startRunWithIntentSource), and the result is persisted in runs.worktree_dir; every later consumer - resume, step diff, startup cleanup, procreap, eject, gatecontext attribution - must read it back through worktrees.RecordedDir and never re-derive it from config, so a mid-flight edit can neither strand a parked run nor point a removal at a directory the run never used. An empty column means the default <NM_HOME>/worktrees/<repoID>/<runID>.
  • worktrees.CheckPlacement is the single policy for an unusable root (inside NM_HOME, inside any registered checkout); config.ValidateWorktreeRoots owns what the config can judge alone. The daemon refuses to start on an unusable placement, so init --worktree-root must refuse exactly the same set or it prints a paste that takes the operator's CLI down, and EVERY init refuses to register a checkout that contains a configured root - the same state reached from the other direction. User-facing semantics live in docs/src/content/docs/reference/global-config.md. Regressions: internal/worktrees, internal/config/config_worktree_roots_test.go, internal/daemon/worktree_roots_test.go, internal/gate/eject_sweep_test.go, internal/cli/init_test.go.

Git on Bare Gate Repos (safe.bareRepository)

  • Agent harnesses and hardened CI inject safe.bareRepository=explicit, which forbids cwd-based discovery of bare repositories. Route every gate git call through git.Run, which detects a bare git dir and prepends --git-dir=<dir>; never shell out to git in a bare gate repo relying on cmd.Dir or -C discovery (issue #362).
  • Startup gate migration is DB-authoritative with a strict validated <id>.git legacy fallback; it must reject non-gates before hook or Git mutation and use git.RunBare so a malformed directory cannot discover an ancestor worktree. Completed migrations carry the content-versioned gate-config stamp and normal restarts must stay filesystem-only for current gates. Regressions: TestMigrateGateConfigsRejectsInvalidDirectoriesAndSkipsCurrentGates, TestColdDetachedStartupProductionGateCardinality.
  • Regressions: TestRunOnBareRepoUnderSafeBareRepositoryExplicit, TestWorktreeAddRemoveOnBareRepoUnderSafeBareRepositoryExplicit, TestInitUnderSafeBareRepositoryExplicit.

gh PR-Targeting From the Bare Gate Repo (internal/scm/github)

  • The daemon runs gh from the detached bare gate repo whose HEAD is the default branch, so every PR-targeting command must name the exact PR explicitly: an empty positional makes gh pr <verb> infer the cwd branch (main) and return no pull requests found for branch main even when the feature PR's checks are green. GetChecks, GetPRState, GetMergeableState, and UpdatePR route through the shared prSelector (number, else URL, else fail closed) - never append a bare pr.Number/pr.URL that can be empty. This is the gh analogue of the git bare-gate-repo trap above.
  • Regressions: TestGetChecksTargetsKnownPRByURLWhenNumberMissing, TestPRTargetingReadsFailClosedWithoutIdentity, TestPRStateAndMergeableTargetKnownPRByURL, TestUpdatePRTargetsKnownPRByURLWhenNumberMissing, TestUpdatePRFailsClosedWithoutIdentity.

Post-Receive Hook Gate Path Resolution (internal/git/hook.go)

  • The hook's --gate value must never come from a bare $(pwd): Git can invoke post-receive from a cwd that collapses to . (issue #269), which the daemon rejects and the pipeline silently never starts. The hook script resolves an absolute gate dir (git first, hook location fallback), and normalizeNotifyGatePath in internal/cli/daemon_cmd.go is an independent second layer that absolutizes whatever an already-installed older hook sends.
  • Regressions: TestPostReceiveHook_ResolvesAbsoluteGateDir, TestPostReceiveHook_FallsBackToHookLocationForGateDir, TestNormalizeNotifyGatePathResolvesLegacyDotGate.

Signals

GitHub stars
8k
Forks
855
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
gate-worktree-git-safety
Source
github.com/kunchenguid/no-mistakes