Add parse-time directory validation helper for worktree.dir #321
Labels
No labels
batch
bug
chore
documentation
enhancement
pi-loop
ready
resume
No milestone
No project
No assignees
1 participant
Notifications
Due date
Dependencies
No dependencies set
Reference
david/pi-loop#321
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Add a filesystem validation helper for
worktree.dir(tilde, absolute, not-inside-repo, exists-as-directory) and wire it intoloadConfigFile, so a misconfigured folder fails at config-parse time as aConfigFileError— before any git call or agent session.Background
A preceding step in this series adds the
worktree:section to the parser. ADR-022 §4 requires everydirproblem to be found at config-parse time — before arg parsing finishes, before origin detection, before any git call or agent session — raised asConfigFileError, which flows through the existingreportConfigFileErrorpath insrc/cli/services/runCli.ts→error.jsonwithkind: config, a stderr message, exit 1.Key constraints:
parseConfigFileYaml(contents, filePath)(insrc/config/services/configFile.ts) stays a pure function — the filesystem checks live in a small helper thatloadConfigFilecalls after successful parsing.loadConfigFile(cwd)is currently synchronous (readFileSync+ pure parse). Keep it synchronous: usestatSync/realpathSyncinside the helper so every existing caller and test stays unchanged.enabledresolves totrue). An absent or disabled section must never touch the filesystem.fs.accesswithW_OKis advisory (ACLs, containers, root can all make it lie); git's own error remains the authority for a read-only folder.--helpand--list-modelsbypass config loading entirely (theinformationalModecheck inrunCli), so a brokendirnever blocks them.Check order (each its own
ConfigFileError):Not tilde:
dir === '~'ordir.startsWith('~/')→ a dedicated, helpful message telling the operator to write the full path (e.g./home/<user>/wt). Rationale: the shell expands~; YAML andfsdo not —path.isAbsolute('~/wt')isfalse, so accepting it would silently create a folder literally named~. Tilde expansion is rejected because the home directory differs per user and per host.Absolute:
!path.isAbsolute(dir)→ error. No relative-to-repo or relative-to-cwd resolution (in bare mode, cwd is the bare repo, so a relative path would mean something surprising).Not inside the repo: resolve both paths and compare with a separator-aware prefix test:
Reject when
inside. Do not usestartsWith(root)alone —/srv/wt2must not match/srv/wt. The check is one-directional: adirthat is a parent of the repo is allowed. Rationale: a worktree inside the working tree pollutesgit statusand every diff, and risks nesting one worktree in another.Exists and is a directory:
stat(resolved); a throw or!isDirectory()→ error naming the path.Symlinks: a symlinked
dirthat resolves inside the repo must be rejected — where both exist, comparefs.realpathSyncof both paths for the prefix test. Record the choice in a code comment.Depends on: #320
Implementation Details
New file
src/config/helpers/validateWorktreeDir.ts. Note:src/config/helpers/does not exist yet — config code currently lives insrc/config/services/andsrc/config/types/; this step creates thehelpersdirectory (with its re-export).(An async variant with
Promise<...>was sketched in the plan; prefer the synchronousstatSyncversion soloadConfigFilekeeps its current synchronous signature. The injectable seam is the test contract.)loadConfigFile(cwd): afterparseConfigFileYamlsucceeds, when(result.worktree.enabled ?? true)andresult.worktree.dir !== undefined, callvalidateWorktreeDir({ dir: result.worktree.dir, repoRoot: cwd }). The helper'sConfigFileErrorpropagates unchanged.Export the helper from
src/config/index.ts.Acceptance Criteria
ConfigFileErrornaming the path.~and~/xfail with the tilde-specific message.dir === repoRootfails;dirinsiderepoRootfails; a parent of the repo passes; sibling/srv/wt2against root/srv/wtpasses.enabled: falsewith a nonexistentdir→ no error, no filesystem access.dirthat resolves inside the repo fails.loadConfigFilesurfaces the helper'sConfigFileErrorunchanged (verified with a temp-dir test writing a realpiloop-config.yaml).npm run lintandnpm testpass.Test Plan
New test file
src/config/helpers/validateWorktreeDir.test.tsusing a temp directory (created viafs.mkdtempSync, cleaned up after) covering every case above, plus aloadConfigFiletest inconfigFile.test.tsthat writes a broken config into a temp dir. Runnpm test.pi-loop opened and merged a pull request for this issue: #327