You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Only when the --purge argument is provided should the data directories we cleared. The --purge argument should be paired with the uninstall argument. If it is not, then uninstall should only remove the service and not the data.
Gate --uninstall's data deletion behind a new --purge flag
Context
Today --uninstall (internal/install.Uninstall, called from cmd/agent.go:115) always destroys the operator's state: it removes workspace.root, workspace.repos_root, workspace.logs_root, store.path, and claude.usage_cache_path (removeConfiguredStatePaths, internal/install/install.go:431), and — if --install created the dedicated coding-agent-loop system account — runs userdel -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 --purge flag that gates exactly that deletion. Plain --uninstall should remove only the service (unit file, /opt/coding-agent-loop, systemd registration); data directories should only be cleared when --purge is also passed. --purge is only meaningful alongside --uninstall.
Approach
1. cmd/agent.go — the flag
Add purge bool to the flags struct (after uninstall).
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 --purge on its own: add a small validateFlags(f) error called from main immediately after flag.Parse() (before the --print-service short-circuit, so every invocation is checked uniformly) returning --purge only applies to --uninstall (try: sudo coding-agent-loop --uninstall --purge) when f.purge && !f.uninstall. Exit non-zero via the existing fmt.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 --uninstall line says it removes the service only, plus a new sudo coding-agent-loop --uninstall --purge also delete workspace/logs/state data line.
2. internal/install/install.go — gate the destructive steps
Add Purge bool to UninstallOptions, documented as: state paths and the dedicated account's home survive when false.
Rewrite Uninstall's doc comment to separate unconditional steps (stop/disable, remove unitPath, daemon-reload, remove installRoot) from purge-only steps. Keep the existing note that claude.credentials_path is never touched under any flag.
Replace the direct removeConfiguredStatePaths(t.home, opts.ConfigPath, log) call (install.go:312) with a purge-aware helper. Keep loadStatePaths, expandHome, removeDir, removeFile exactly as they are and restructure only the caller:
// resolvedStatePaths returns the directories and files the service was// configured to write, resolved against home.funcresolvedStatePaths(home, fallbackConfigPathstring, logfunc(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.funcapplyStatePaths(home, fallbackConfigPathstring, purgebool, logfunc(string, ...any))
applyStatePaths replaces removeConfiguredStatePaths. Its non-purge branch still resolves the paths (so the operator is told where their data lives) and logs one line per retained path plus re-run with --uninstall --purge to delete this data. Resolution must still happen beforeinstallRoot is removed, since installedConfigPath lives there — keep the call in its current position in Uninstall.
Gate the dedicated-user block (install.go:337-350) on opts.Purge. When not purging, keep the user.Lookup(dedicatedUser) check but only log that the account and /home/coding-agent-loop were left in place, naming --uninstall --purge as the way to remove them. Recommendation: do not run userdel at all when not purging — userdel without -r orphans the home to a dangling uid, and a later --install recreates 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.
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 valid config.json for go:embed).
Add a purge target using the same shim, invoking sudo $(BINARY) --uninstall --purge, with a ## purge: help line marking it destructive. Either factor the shim into an internal ensure-binary prerequisite shared by both targets, or repeat the four-line block — either is acceptable.
4. README.md
CLI flags table (~line 152): change the --uninstall row to "stop, disable, and remove the systemd unit and /opt/coding-agent-loop; leaves all data in place", and add a --purge row: "only with --uninstall: also delete the configured workspace/logs/state paths and the dedicated service account's home".
"To remove everything --install created" 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 --uninstall the same way --install was run.
Quick start (~line 118) needs no change.
5. Tests — internal/install/install_test.go
Existing coverage (TestUninstallRequiresRoot, removeDir/removeFile/loadStatePaths tests) stays. Add tests against the new applyStatePaths seam, which needs no root:
TestApplyStatePathsRemovesWhenPurging — temp home with .agent-loop/work, .../logs, .../state.db; point installedConfigPath at a temp config via the existing setInstalledConfigPathForTest helper; 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 / --uninstall to 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-loop account 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 --uninstall does not fully reverse --install in that path.
/opt/coding-agent-loop/config.json is still deleted by plain --uninstall, since it goes with os.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 remove installRoot/bin and the unit while leaving config.json behind (note installConfig already refuses to clobber an existing config on re-install).
--purge alone 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} and state.db.
sudo bin/coding-agent-loop --uninstall → unit gone, /opt/coding-agent-loop gone, ~/.agent-loopand its contents intact, log lines naming each retained path.
make install again, then sudo bin/coding-agent-loop --uninstall --purge → same service removal plus ~/.agent-loop gone; confirm ~/.claude/.credentials.json still exists.
In the no-SUDO_USER path (run as root in a container): non-purge leaves the coding-agent-loop account and /home/coding-agent-loop; purge removes both.
Reply with exactly implement to 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, model claude-opus-5, cost $0.9100
Activity
ableinc commentedon Aug 29, 2026
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 flagpurge boolto theflagsstruct (afteruninstall).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").--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.install.Uninstall(install.UninstallOptions{ConfigPath: ..., Purge: f.purge, Log: ...}).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 stepsAdd
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: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.
Makefilepurgeto.PHONY(line 17).## 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).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--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".--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.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
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.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
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.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.9100ableinc commentedon Aug 30, 2026
implement
ableinc commentedon Aug 30, 2026
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