Repository navigation
writ System scope: TargetSystem returns / on Windows, and adopt classifies against %SystemRoot% #761
Description
Feature: #762 — The writ lifecycle surface: reconcile, scopes, and the vocabulary settlement
On Windows, three parts of writ disagree about where the System scope points. On Unix two of them agree by
accident and the third is unreachable.
Ruled 2026-08-31
System targets / on Unix and %SystemDrive%\ on Windows. Period.
What the code does
| Site | Windows root | Unix root |
|---|---|---|
cmd/writ/writ/layer.go:55 TargetSystem() |
/ — no Windows branch exists |
/ |
cmd/writ/writ/adopt/batch.go:274 inferScope |
%SystemRoot% — that is C:\Windows |
/ |
| the ruling | %SystemDrive%\ — that is C:\ |
/ |
TargetSystem():
func TargetSystem() string {
if configured := viper.GetString("writ.targets.system"); configured != "" {
return expandPath(configured)
}
return "/"
}There is no runtime.GOOS branch. On Windows a bare / is drive-relative — it anchors to whichever drive
the process happens to be standing on, which is the exact defect OpenTree's absolute-path requirement
exists to prevent (see SetStoreRoot, which absolutizes for the same reason).
inferScope's doc comment:
Unix: paths under
$HOMEare Home, paths under/are System. Windows: paths under%USERPROFILE%are
Home, paths under%SystemRoot%are System.
%SystemRoot% is C:\Windows. %SystemDrive%\ is C:\. So on Windows, writ adopt classifies a file as
System only if it sits under C:\Windows, while writ deploy would write System files to a drive-relative
/. A file adopted into System/ cannot be deployed back to where it came from, and a file under
C:\ProgramData is classified Home or not at all.
Why it has not been noticed
Unix has no %SystemDrive%, and / is correct there, so both sites agree on the platform the suite is
developed on. The divergence exists only on Windows, and writ deploy to System on Windows is not
something the scenario tests exercise.
Fix
TargetSystem() branches on runtime.GOOS: %SystemDrive%\ on Windows, / elsewhere, with the
writ.targets.system override winning on both as it does today. inferScope classifies against the same
function rather than against its own idea of the root -- the duplication is what let them drift.
Verification
- On Windows,
TargetSystem()returns%SystemDrive%\and never a drive-relative path. writ adoptclassifiesC:\ProgramData\xandC:\Windows\yalike as System, because both are under
%SystemDrive%\.- A file adopted into
System/deploys back to the path it was adopted from -- the round trip that is
broken today. inferScopeandTargetSystemcannot disagree, because only one of them decides.- The Unix behaviour is unchanged.
Neighbours
- The
writ.targets.*config key is in the shipped defaults file and absent from the schema, the same shape
as #746. It is renamed towrit.scopes.*by the
vocabulary ruling, which is tracked separately. TargetSystemandTargetHomeare renamedScopeSystem/ScopeHomeby that same ruling. This issue is
about the value they return, not their names, and is independently fixable.
Activity
Plan: the writ lifecycle surface — reconcile, scopes, and the vocabul…
David-Noble-at-work commentedon Oct 2, 2026
Lane 7 of #916 (#926) fixed
System's root; adopt's half is openPhase 2 of lane 7 (#926, 8b453dc on
feature/925-scope-not-target) rebuilt the scope model incmd/writ/writ/layer.go:ScopeSystem(), formerlyTargetSystem(), is%SystemDrive%\on Windows and/elsewhere, and awrit.scopeskey named for it relocates it. That settles the table's first row, as ruled above.Adopt's half remains, and the table's second row is not quite what the code does.
%SystemRoot%appears only ininferScope's doc comment (cmd/writ/writ/adopt/batch.go) and inwrit adopt's help (cmd/writ/writ/adopt_cmd.go); the code makes everything outside the home directory System, on every platform, so a file underC:\ProgramDatais classified System, not Home. AndcollectItemtakes System's root to be/itself rather than from the model: on Windows, the drive-relative root this issue describes.Lane 7's plan (
docs/plans/task/926-scope-flag.md, open question 6, not yet ruled) proposes that adopt infer the scope whose root is the deepest that holds the item, overScopeOrder(), and take that scope's root from the model; and that adopt record its scope in lower case, as deploy does. #931's item 4 had handed that inference to lane 7.feat(writ): the operations honor --scope, and a scoped deploy keeps t…
David-Noble-at-work commentedon Oct 2, 2026
Joins PR C as lane 32 of #916; lane 7's phase 4 closes it
Ruled 2026-10-02 (open question 6 of lane 7's plan, #926): adopt infers each item's scope from the scope model, as the scope whose root is the deepest that holds the item, and takes that scope's root from the model. On Windows, System is then
%SystemDrive%\, and a file underC:\ProgramDatais adopted intoProgramData. The run records its scope in lower case, as deploy's runs do. With lane 7's phase 2, which fixedScopeSystem(), that closes this issue when PR C merges.feat(writ): adopt infers each item's scope from the scope model and r…
docs(plan): lane 7 closes, its VM run recorded and its tests against …