Repository navigation
Add a --purge argument #17
Description
Activity
Plan
Gate
--uninstall's data deletion behind a new--purgeflagContext
Today
--uninstall(internal/install.Uninstall, called fromcmd/agent.go:115) always destroys the operator's state: it removesworkspace.root,workspace.repos_root,workspace.logs_root,store.path, andclaude.usage_cache_path(removeConfiguredStatePaths,internal/install/install.go:431), and — if--installcreated the dedicatedcoding-agent-loopsystem account — runsuserdel -r, deleting that account's entire home. There is no way to remove the systemd service while keeping run history, transcripts, cloned repos, and SQLite state.Issue #17 asks for a
--purgeflag that gates exactly that deletion. Plain--uninstallshould remove only the service (unit file,/opt/coding-agent-loop, systemd registration); data directories should only be cleared when--purgeis also passed.--purgeis only meaningful alongside--uninstall.Approach
1.
cmd/agent.go— the flag- Add
purge boolto theflagsstruct (afteruninstall). - Register it:
flag.BoolVar(&f.purge, "purge", false, "with --uninstall: also delete the service's data — workspace.root, workspace.repos_root, workspace.logs_root, store.path, claude.usage_cache_path, and the dedicated service account's home. Without it, --uninstall removes only the service and leaves all data in place"). - Reject
--purgeon its own: add a smallvalidateFlags(f) errorcalled frommainimmediately afterflag.Parse()(before the--print-serviceshort-circuit, so every invocation is checked uniformly) returning--purge only applies to --uninstall (try: sudo coding-agent-loop --uninstall --purge)whenf.purge && !f.uninstall. Exit non-zero via the existingfmt.Fprintf(os.Stderr, ...)/os.Exit(1)pattern. - Pass it through:
install.Uninstall(install.UninstallOptions{ConfigPath: ..., Purge: f.purge, Log: ...}). - Update
usageHeader(cmd/agent.go:48): the--uninstallline says it removes the service only, plus a newsudo coding-agent-loop --uninstall --purge also delete workspace/logs/state dataline.
2.
internal/install/install.go— gate the destructive steps-
Add
Purge booltoUninstallOptions, documented as: state paths and the dedicated account's home survive when false. -
Rewrite
Uninstall's doc comment to separate unconditional steps (stop/disable, removeunitPath,daemon-reload, removeinstallRoot) from purge-only steps. Keep the existing note thatclaude.credentials_pathis never touched under any flag. -
Replace the direct
removeConfiguredStatePaths(t.home, opts.ConfigPath, log)call (install.go:312) with a purge-aware helper. KeeploadStatePaths,expandHome,removeDir,removeFileexactly as they are and restructure only the caller:// resolvedStatePaths returns the directories and files the service was // configured to write, resolved against home. func resolvedStatePaths(home, fallbackConfigPath string, log func(string, ...any)) (dirs, files []string) // applyStatePaths removes those paths when purge is set, and otherwise logs // exactly what was left behind and how to remove it. func applyStatePaths(home, fallbackConfigPath string, purge bool, log func(string, ...any))
applyStatePathsreplacesremoveConfiguredStatePaths. Its non-purge branch still resolves the paths (so the operator is told where their data lives) and logs one line per retained path plusre-run with --uninstall --purge to delete this data. Resolution must still happen beforeinstallRootis removed, sinceinstalledConfigPathlives there — keep the call in its current position inUninstall. -
Gate the dedicated-user block (install.go:337-350) on
opts.Purge. When not purging, keep theuser.Lookup(dedicatedUser)check but only log that the account and/home/coding-agent-loopwere left in place, naming--uninstall --purgeas the way to remove them. Recommendation: do not runuserdelat all when not purging —userdelwithout-rorphans the home to a dangling uid, and a later--installrecreates the account with a possibly different uid, leaving the retained data unreadable. Keeping account and home together is the only variant where "keep the data" actually holds. -
Everything else stays unconditional:
systemctl disable --now, removingunitPath,daemon-reload,os.RemoveAll(installRoot).
3.
Makefile- Add
purgeto.PHONY(line 17). - Reword the
## uninstall:help comment to "stop, disable, and remove the service; leaves data in place". Keep the existing build-if-missing shim and its comment verbatim (it exists because uninstalling must work without a validconfig.jsonforgo:embed). - Add a
purgetarget using the same shim, invokingsudo $(BINARY) --uninstall --purge, with a## purge:help line marking it destructive. Either factor the shim into an internalensure-binaryprerequisite shared by both targets, or repeat the four-line block — either is acceptable.
4.
README.md- CLI flags table (~line 152): change the
--uninstallrow to "stop, disable, and remove the systemd unit and/opt/coding-agent-loop; leaves all data in place", and add a--purgerow: "only with--uninstall: also delete the configured workspace/logs/state paths and the dedicated service account's home". - "To remove everything
--installcreated" section (~lines 845-871): show both commands (--uninstall,--uninstall --purge,make uninstall,make purge) and split the numbered list into "always" steps (today's 1, 3, 4) and "only with--purge" steps (today's 2 and 5), keeping the existing prose about non-default state paths and about running--uninstallthe same way--installwas run. - Quick start (~line 118) needs no change.
5. Tests —
internal/install/install_test.goExisting coverage (
TestUninstallRequiresRoot,removeDir/removeFile/loadStatePathstests) stays. Add tests against the newapplyStatePathsseam, which needs no root:TestApplyStatePathsRemovesWhenPurging— temp home with.agent-loop/work,.../logs,.../state.db; pointinstalledConfigPathat a temp config via the existingsetInstalledConfigPathForTesthelper;purge=true; assert the paths are gone.TestApplyStatePathsKeepsDataWhenNotPurging— same fixture,purge=false; assert every path still exists and that the log callback fired.TestApplyStatePathsHonoursConfiguredPaths— config naming non-default absolute paths under a temp dir,purge=true; assert those exact paths are removed, locking in that purge follows the config rather than assuming~/.agent-loop.
Risks / decisions for the reviewer
- Behaviour change for existing users. Anyone relying on
make uninstall/--uninstallto wipe state now needs--purge. That is the explicit intent of the issue; the README and usage-text changes are what make it discoverable. - Dedicated-user handling (main judgement call). This plan leaves the
coding-agent-loopaccount and its home in place when not purging. The alternative — delete the account, keep the home — is rejected because it strands data behind an orphaned uid. Worth a reviewer nod, since it means plain--uninstalldoes not fully reverse--installin that path. /opt/coding-agent-loop/config.jsonis still deleted by plain--uninstall, since it goes withos.RemoveAll(installRoot)as today. I read the issue's "data directories" as the state paths, not the installed config copy. If the reviewer wants the config preserved on a non-purge uninstall, the change would be to removeinstallRoot/binand the unit while leavingconfig.jsonbehind (noteinstallConfigalready refuses to clobber an existing config on re-install).--purgealone errors rather than warning — the issue calls it a paired flag, and a silent no-op on a destructive-sounding flag is worse than a clear failure.
Verification
go build ./... && go vet ./... go test -race ./internal/install/... make test # full suite bin/coding-agent-loop --purge # expect exit 1 with the pairing error bin/coding-agent-loop --help # usage shows --purge and the reworded --uninstall
Manual end-to-end (throwaway VM/container with systemd; these steps need root):
make install, let the service create~/.agent-loop/{work,repos,logs}andstate.db.sudo bin/coding-agent-loop --uninstall→ unit gone,/opt/coding-agent-loopgone,~/.agent-loopand its contents intact, log lines naming each retained path.make installagain, thensudo bin/coding-agent-loop --uninstall --purge→ same service removal plus~/.agent-loopgone; confirm~/.claude/.credentials.jsonstill exists.- In the no-
SUDO_USERpath (run as root in a container): non-purge leaves thecoding-agent-loopaccount and/home/coding-agent-loop; purge removes both.
Reply with exactly
implementto approve this plan and start the change. Reply with anything else and the plan will be revised to address it.coding-agent-loop run
fb571323-4ec1-4435-bd4c-961adace0b22, modelclaude-opus-5, cost $0.9100- Add
- addedagent-plannedManaged by coding-agent-loopManaged by coding-agent-loopand removed
on Aug 29, 2026 implement
Opened a draft pull request for this issue: #20
Tests failed (
make test) — see the PR for output.Comment
implementagain if you want another attempt at this issue.coding-agent-loop run
2f5f1598-467b-4d0f-a207-27131c31f3e2- added and removedagent-plannedManaged by coding-agent-loopManaged by coding-agent-loop
on Aug 30, 2026
Only when the
--purgeargument is provided should the data directories we cleared. The--purgeargument should be paired with the uninstall argument. If it is not, then uninstall should only remove the service and not the data.