Repository navigation
omp: agent resource hygiene (scratch, tidy, docks-kit-omp plugin) #89
Description
Goal
Agents remove what they create, while removal of the home directory and / stays blocked. The owner uses only omp, so the Claude and Codex SoT files stay unchanged.
End state:
- docks-kit gains
scratch(guarded temporary directories) andtidy(safe leftover sweep plus a read-only--reportof other disk and memory use). bash.patternsdenies the bulk-deletion forms that can empty the home directory (find -delete,find -exec rm,xargs rm, in every position).docks-kit sync ompmaterializes and links a kit-owned omp plugin,docks-kit-omp, with an extension (native toolsscratch_new,scratch_remove,tidy_report; creation reminders; a stop check; the automatic sweep), theworkspace-hygieneskill, and the/cleanupcommand.- The current workstation backlog is reclaimed with owner confirmation, one
askper group. - The open omp session in the docks repository receives the plan-lifecycle teardown change request through intercom and owns that change (owner instruction, 2026-10-21).
Fixed requirements:
- Temporary directories are removed only through
docks-kit scratch removeorscratch_remove, both of which check paths in code. - An agent may run
git worktree remove --force -- <path>once, after checks; never--forcetwice. - No new deny or prompt rules for
nohup,setsid,disown, ordocker; those are prompt rules, skill text, and reminders only. - At a main-session start the plugin runs
docks-kit tidy --applyin the background, at most once per 12 h. The sweep sends SIGTERM to orphaned processes in agent directories.
Mode: plan-and-implement
Research
Guards (SoT/.omp/config.yml, ompYaml.ts mergeBashPatterns, cli/test/lib/ompBashPatterns.ts):
bash.patternsare anchored text globs;*matches any text including/and spaces, so no glob can express "inside the temporary directory". The merge emits kit denies first, so a kit allow never precedes a kit deny. ompCRITICAL_BASH_PATTERNSdoes not coverfind,xargs, ordocker.- Today
find <home> -delete,cd <home> && find . -delete,find ~ -type f -exec rm {} +, andfind ~ -type f | xargs rmmatch no deny rule and run under yolo.cli/test/unit/ompBashPatterns.test.tslistsxargs rm,find . -delete, andfind . -name '*.tmp' -exec rm {} +as ALLOWED. git worktree removerefuses the main worktree and unregistered paths; without--forceit refuses untracked or modified files; ignored files do not block it.
Workstation state (measured 2026-10-21): one repository has 68 linked worktrees (46 merged, 10.4 GB), 72 merged local branches, and 1 stash. Agent-invented cache directories hold about 14.6 GB. The system temporary directory is tmpfs with 3.9 GB used. Package caches total about 12 GB; Trash holds 2.1 GB with GNOME remove-old-trash-files false. Docker is rootless with 10 exited containers (8 per-worktree MySQL stacks) and 7.7 GB build cache; two named volumes hold owner data. Nine omp processes use 3.1 GB, swap use is 6.4 GB. The user systemd is the subreaper that adopts orphans.
omp plugin system (omp 18.12.5; omp://plugin-manager-installer-plumbing.md, omp://marketplace.md, omp://skills/authoring-extensions.md, upstream test packages/coding-agent/test/discovery/omp-plugins.test.ts, can1357/oh-my-pi#1498):
omp plugin link <dir>symlinks a local package into<plugins>/node_modules/<name>and adds an enabled lockfile entry. A package withpackage.jsonomp.extensionsloads those modules, and its siblingskills/,commands/,tools/,hooks/,rules/,prompts/, and.mcp.jsonare discovered.omp -e <dir>loads it for one run.omp plugin list --jsonreturns{ npm, marketplace }; an npm row carriesname,version, andpath(thenode_modulessymlink).pi.registerTool({ name, label, description, parameters, execute })withpi.zodschemas; defaultloadModediscoverable, defaultapprovalexec. Events:tool_result(toolName,input,content,isError; may returnadditionalContext; fires in subagents),session_stop(main only; may return{ continue: true, additionalContext };stop_hook_activemarks a continuation; cap 8),session_start(ctx.agent.kindmain or sub). Module state is shared per omp process; handlers have a 30 s budget.- Current omp values:
task.agentIdleTtlMs420000,browser.idleCloseSec1800.
Kit code:
- CLI registration:
cli/src/main.ts(Command.withSubcommandsplus the manual root listing) andcli/src/argv.ts COMMANDS. Positional action words followcommands/toolchain.ts.engine.ts bail(message, code)reports usage errors;process.exitCode = 1reports failures. exec.ts spawnProcessreturns{ exitCode, stdout, stderr, error? }and ignores stderr by default;exec.ts pslash-joins paths.harnesses.ts engineHomeresolves home.commands/omp.tsbuilds the overlay directory asp(home, ".cache", "docks-kit").ompPlugins.ts syncPluginsreads the inventory once (installedPluginIdsFromCli, npm map of name to version), runs commands throughrunPluginCommand, records failures withrecordFailure, and prints[dry-run]lines without running omp in a dry run.ompFileDeploy.ts ensureDirectorycreates mode 0700 directories.payload.tsexportspayloadText,payloadPaths(prefix), andpayloadDisplayPath.- The SoT payload is an explicit list (
cli/scripts/generate-sot-payload.ts TEXT_PAYLOAD_PATHS);cli/test/unit/payload.test.tsasserts everySoT/file is listed.cli/tsconfig.jsontypechecks only the Claudebin/*.mjsSoT files today. - Unit tests run on Linux, macOS, and Windows (
.github/workflows/parity.yml). Goldens snapshot the deployed home tree (cli/test/goldens/mutation.json) and dry-run and root-listing output (cli/test/goldens/dryrun.json).
plan-lifecycle 0.14.1: yolo removes its worktree only after Merge now; plan-manager never removes the linked worktree or stops stacks and processes. Agent worktrees live under $XDG_DATA_HOME/agent-worktrees/<repo>/<slug>.
Decisions:
- Durable fix: code-checked removal (
scratch remove,tidy) plus deny rules for bulk deletion. It replaces the current state where bulk deletion is allowed and cleanup is manual. - The plugin source lives in
SoT/.omp/plugin/, ships in the payload, and is materialized to~/.docks-kit/omp-plugin(kit-owned, no backups) and linked withomp plugin link. - Thresholds: worktree, stack, and orphan idle 24 h; report process age 12 h; scratch expiry 7 d; sweep interval 12 h; lock stale 1 h; log rotation 256 KiB;
task.agentIdleTtlMs120000;browser.idleCloseSec600. tidynever passes--forcetogit worktree removeand never removes named volumes;docker rm --volumesremoves anonymous volumes only.- Deployment waits for a release: the owner runs
docks-kit update. Do not sync omp from the checkout while the globaldocks-kitlacks the new commands. - Process detection fails closed (plan review rounds 1 and 2).
TidyHost.processesreturnsundefinedwhen the host cannot list processes with their working directories. Linux readsps -u <uid> -o pid=,ppid=,etimes=,rss=,comm=,args=(every process of the user, with or without a terminal) plus/proc/<pid>/cwd; macOS readsps -x -U <uid> -o pid=,ppid=,etime=,rss=,comm=,args=(-xincludes processes without a controlling terminal) pluslsof -nP -a -u <uid> -d cwd -Fpn; Windows and any failed probe giveundefined. Withundefined,planTidycreates noremove-worktree,stop-containers,stop-process, orremove-scratchaction, keeps those candidates with the reasonprocess list unavailable, and adds a note. Onlyremove-containersfor a directory that is gone still runs. - No command interpreter in the extension (plan review round 1). Every extension subprocess uses
execFileorspawnwithoutshell. The extension resolvesdocks-kiton PATH itself; on Windows it accepts only a resolved.exeand refuses a.cmdor.batshim with the not-on-PATH error, so a tool argument never reachescmd.exe. - One sweep per interval across processes (plan review round 1).
session_startclaimstidy.stamp.claimwithopenSync(path, "wx"); a claim older than 60 s is stale and is replaced. Only the claim winner re-reads the stamp mtime, writes the stamp whensweepDuestill holds, spawnsdocks-kit tidy --apply, and then releases the claim. A loser spawns nothing. - Production rule (owner decision, 2026-10-21). Local workstation resources are always development, so
docks-kit tidyand its automatic sweep stay as designed. Cleanup of a remote resource that an agent reaches through SSH or a provider CLI (for examplewrangler,vercel,netlify,fly,supabase,kubectl, or a remote Docker host) runs only when its target is named local, development, dev, test, homolog, homologation, staging, or preview, the plan-lifecycle definition of non-production. Every other target is production: its cleanup runs only after anaskimmediately before it, must be rare, and never runs in a subagent. A yolo invocation does not answer that ask.wrangler deletewithout--envacts on the top-level Worker, which is usually production. The rule goes intoSoT/.omp/AGENTS.md, theworkspace-hygieneskill,/cleanup, and the extension's remote-resource and stop reminders. The plan-lifecycle teardown request to the docks session carries the same rule. - Local Docker endpoint only (plan review rounds 4 and 5). Ordinary Docker commands use
DOCKER_HOST,DOCKER_CONTEXT, or the selected context, so a compose working-directory label inside agent territory does not prove a local daemon. Before any container rule, tidy reads the effective endpoint withdocker context inspect --format '{{.Endpoints.docker.Host}}', which honorsDOCKER_HOSTandDOCKER_CONTEXT. It accepts only a local-machine endpoint:unix:///<absolute socket path>(Linux and macOS) ornpipe:////./pipe/<name>(Windows; the.server name is the local machine). A pipe with any other server name, such asnpipe:////host/pipe/docker_engine, names a remote server and is refused. Tidy passes the accepted endpoint as--host <endpoint>to every later Docker call in the plan, the apply, and the report. Any other endpoint (ssh://,tcp://, a remote pipe, an empty value, or a failed inspect) skips every container rule and the Docker report section, with the notedocker endpoint <value> is not local; skipped container rules. A remote Docker resource then falls under the production rule above. - Unreadable process directories (owner decision after code review round 1; plan review round 7). The kernel hides the working directory of some of the user's own processes; on this workstation 4 of 169:
systemd --user,(sd-pam), and twossh-agent. A process that exits during the probe is dropped from the list. A row namedsystemd,(sd-pam),ssh-agent, orgpg-agentstays in the list with an unknown working directory: it keeps its PID and name, so the subreaper rule still recognizessystemd, and it protects no directory, because no agent starts these processes from a worktree. Any other live process whose working directory cannot be read makes the process list unavailable, with the fail-closed result above. - Code review round 1 repairs (plan review round 7). The shared removal function
scratch.ts removeTemporaryPath, whichscratch remove,scratch_remove, and the tidy scratch rule all use, deletes a path only through a trusted directory chain. Every directory from/down to the parent of the target, the allowed root included, must be owned by the user or by root, and must not be writable by group or others, except a sticky directory (such as/tmp) whose next path component the user or root owns. POSIX lets only the owner of an entry, the owner of a sticky directory, or root rename an entry in a sticky directory, and only a user who can write a directory can rename entries in it, so no other user can replace a directory on that chain after the check. A root whose own chain fails this rule is dropped. Windows has no POSIX owner bits, so these ownership checks do not apply there. The function also refuses a target equal to any allowed root (nested roots such as aTMPDIRinside/tmp), and it checks the final entry without trailing separators, so<link>/is refused as a symbolic link. Tidy treats a path whose state it cannot read as unknown, never as absent. The tidy lock and the sweep claim carry an owner token, are released only by their owner, and replace a stale entry only while they hold a takeover mutex created withwx. The extension keeps Docker daemon options (--context,--host,DOCKER_HOST,DOCKER_CONTEXT) in cleanup commands and existence checks, and keeps acdinside a subshell or a command substitution in that scope.
Detailed design (function signatures, rule order, refusal reasons, tests) is carried in the Steps rows below.
Steps
| # | Id | Task | Files | Depends | Effect | Status | Done when |
|---|---|---|---|---|---|---|---|
| 1 | guards | Insert 17 bulk-removal deny rules after * rm *--recursive (find -delete bare, */find, * find; -exec, -execdir, -ok, -okdir with rm and */rm; six xargs rm shapes) with a comment; rewrite the patterns: comments; add task.agentIdleTtlMs: 120000 and browser.idleCloseSec: 600. Move the five find/xargs rm cases to DENIED, add the home-emptying cases, and add ALLOWED cases for plain find, xargs grep, docks-kit scratch, docks-kit tidy, and one git worktree remove --force --. |
SoT/.omp/config.yml, cli/test/unit/ompBashPatterns.test.ts |
- | local | done | bunx vitest run cli/test/unit/ompBashPatterns.test.ts passes, including the ordering and no-rm tests |
| 2 | scratch | Add engine-native/scratch.ts (kitCacheDir, scratchRoot, createScratchDir with label regex and mode 0700, allowedRemovalRoots that drops roots equal to or containing home or /, removeTemporaryPath with the ordered checks: missing, symlink, strictly inside a root, contains home, owner uid, same device, then rmSync). Add commands/scratch.ts (new <label>, remove <path>...); register it in main.ts and argv.ts; use kitCacheDir in commands/omp.ts; add scratch.test.ts in a sandbox. |
cli/src/engine-native/scratch.ts, cli/src/commands/scratch.ts, cli/src/commands/omp.ts, cli/src/main.ts, cli/src/argv.ts, cli/test/unit/scratch.test.ts, cli/test/goldens/dryrun.json |
1 | local | done | bunx vitest run cli/test/unit/scratch.test.ts passes and the root-listing golden shows the new row |
| 3 | tidy | Add engine-native/tidy.ts (injected TidyHost; agentWorktreeRoot; listWorktreeCandidates; planTidy with home guard, path normalization, in-use paths, worktree checks: admin dir, lock, clean status, default ref, merged, idle 24 h, not in use; container rules limited to agent territory; orphan rule; scratch idle 7 d; applyTidy with lock, ordered actions, docker rm --force --volumes before git worktree remove without --force, prune, log rotation). Process detection follows the fail-closed decision in Research: Linux and macOS probes, undefined elsewhere or on probe failure, and no in-use-dependent action without a process list. Docker calls follow the local-endpoint decision in Research: one verified local-machine endpoint (unix:///<path> or npipe:////./pipe/<name>) passed as --host, and no container rule or Docker report for any other endpoint. Add read-only engine-native/tidyReport.ts (Memory, Temporary directories, Processes, Docker, Package and tool caches, Trash, Git, Agent histories, Scheduled jobs and services, Global installs). Add commands/tidy.ts (--apply, --repo, --report); register it. Tests use real git in a sandbox and spawned child processes for PIDs, plus a case where processes returns undefined and the plan holds no in-use-dependent action, and cases where the Docker endpoint is ssh://, tcp://, or a remote pipe (npipe:////host/pipe/docker_engine) and the plan holds no container action. |
cli/src/engine-native/tidy.ts, cli/src/engine-native/tidyLock.ts, cli/src/engine-native/tidyReport.ts, cli/src/commands/tidy.ts, cli/src/main.ts, cli/src/argv.ts, cli/test/unit/tidy.test.ts, cli/test/unit/tidyLock.test.ts, cli/test/unit/tidyReport.test.ts, cli/test/lib/gitSandbox.ts, cli/test/goldens/dryrun.json |
2 | local | done | bunx vitest run cli/test/unit/tidy.test.ts cli/test/unit/tidyReport.test.ts passes, including the unavailable-process-list case and the remote-endpoint cases |
| 4 | plugin | Add SoT/.omp/plugin/ (package.json with omp.extensions; self-contained extensions/hygiene.ts with splitShellSegments, detectResources, cleanupCommand, creationReminder, stopReminder, sweepDue, claimSweep, three tools, and tool_result, session_stop, session_start handlers; skills/workspace-hygiene/SKILL.md; commands/cleanup.md). Subprocesses run without a shell, and the sweep uses the cross-process claim, both as decided in Research. The skill, /cleanup, the remote-resource hint, and the stop reminder carry the production rule from Research. List the four files in TEXT_PAYLOAD_PATHS; typecheck the extension. Add engine-native/ompKitPlugin.ts (syncKitPlugin materializes to ~/.docks-kit/omp-plugin with the kit version, mode 0700/0600, stale-file removal) and call it from ompSync. In ompPlugins.ts, keep the npm row path and link the plugin when its path does not resolve to the materialized directory. Add both plugin tests; the extension test proves that two concurrent claimSweep calls give one winner and that an argument with shell metacharacters reaches the child process literally. |
SoT/.omp/plugin/package.json, SoT/.omp/plugin/extensions/hygiene.ts, SoT/.omp/plugin/skills/workspace-hygiene/SKILL.md, SoT/.omp/plugin/commands/cleanup.md, cli/scripts/generate-sot-payload.ts, cli/tsconfig.json, cli/src/engine-native/ompKitPlugin.ts, cli/src/engine-native/ompSync.ts, cli/src/engine-native/ompPlugins.ts, cli/test/unit/ompKitPluginExtension.test.ts, cli/test/unit/ompKitPlugin.test.ts, cli/test/unit/ompRemovals.test.ts, cli/test/unit/ompPluginInventory.test.ts, cli/test/lib/goldenExecution.ts, cli/src/generated/sotPayload.ts, cli/test/goldens/mutation.json, cli/test/goldens/dryrun.json |
3 | local | done | Plugin tests, payload.test.ts, and bun run typecheck pass; goldens gain the plugin files, the cp lines, and the omp plugin link line; the skill, /cleanup, and the reminders name the production rule |
| 5 | prompt_rules | Replace the ## Removing files body in the omp prompt SoT with the scratch and worktree removal rules, and add ## Cleaning up what you create with the production rule from Research. |
SoT/.omp/AGENTS.md, cli/src/generated/sotPayload.ts, cli/test/goldens/mutation.json |
4 | local | done | Regenerated payload and mutation golden carry the new text, including the production rule |
| 6 | release_gate | Set package.json to 0.21.0; update CHANGELOG.md, README.md, root AGENTS.md layout and omp notes, cli/docs/command-guards.md, cli/docs/flags.md, and the omp-sync-context skill; regenerate; run the full gate. |
package.json, CHANGELOG.md, README.md, AGENTS.md, cli/docs/command-guards.md, cli/docs/flags.md, .agents/skills/omp-sync-context/SKILL.md, cli/src/generated/sotPayload.ts, cli/test/goldens/mutation.json, cli/test/goldens/dryrun.json |
5 | local | done | bun run check exits 0 |
| 7 | workstation | From the linked-branch checkout: run the smoke checks; show the tidy dry run through ask before the first sweep; run the extension sweep check; then reclaim the backlog with one ask per group (worktrees per repository through tidy --repo, agent-invented cache directories moved into the scratch root, old temporary entries through scratch remove, exited containers and build cache, package caches, merged branches, GNOME housekeeping). Never touch named volumes, the stash, or omp processes. |
- | 6 | local | done | Each approved group reports its result; declined groups are listed |
| 8 | docks_issue | By owner instruction (2026-10-21), send the teardown change request (summary, gaps, proposal, acceptance, and the docks-kit interfaces it can name) to the open omp session in the docks repository through intercom, instead of gh issue create. That session owns the plan-lifecycle change and its publication. Answer its questions through intercom. |
- | 6 | publish | done | The docks session reports its plan issue, branch, or pull request URL |
Acceptance
| ID | Command | Expected |
|---|---|---|
| gate | bun run check |
Exit 0; new tests scratch, tidy, tidyReport, ompKitPluginExtension, ompKitPlugin and the changed ompBashPatterns pass |
| runtime_posix | bun run test:runtime:posix |
Exit 0 |
| sync_dry_run | ./docks-kit sync omp --dry-run |
Prints the four plugin cp lines and [dry-run] omp plugin link <plugin dir>; no deployed file under the omp agent directory or the kit plugin directory changes |
| scratch_smoke | S=$(./docks-kit scratch new smoke); mkdir -p "$S/a/b"; ./docks-kit scratch remove "$S" |
Path under ~/.cache/docks-kit/scratch/smoke-* with mode 700; prints removed <path>, exit 0; the path is gone |
| scratch_refuse | ./docks-kit scratch remove <home>; ./docks-kit scratch remove /tmp; symlink operand and <link>/ operand |
Each prints refused ... and exits 1; the home, the temporary root, and the symlink target survive |
| tidy_dry | ./docks-kit tidy |
remove and keep lines with reasons and the dry-run line; the worktree count of the largest repository is unchanged |
| tidy_report | ./docks-kit tidy --report |
Sections Memory, Docker, Package and tool caches, Trash, Git with values near the measured ones; container and branch counts unchanged |
| guard_smoke | omp --config SoT/.omp/config.yml -p with find /tmp/docks-smoke-none -delete, then with printf x | xargs echo |
The first bash call is denied by policy; the second runs |
| plugin_smoke | omp -e SoT/.omp/plugin -p with a PATH wrapper for the branch docks-kit: call scratch_new, then scratch_remove, then read skill://workspace-hygiene |
The answer reports the path, its removal, and # Workspace hygiene; no tool-smoke-* directory remains |
| reminder_smoke | omp -e SoT/.omp/plugin -p 'Run docks-kit scratch new ext-smoke and tell me the path.' |
The session transcript contains You created the scratch directory and These resources from this session still exist: |
| cleanup_command | Interactive omp -e SoT/.omp/plugin |
/cleanup appears in the slash-command list |
| sweep_smoke | Trash the stamp, then omp -e SoT/.omp/plugin -p 'Reply OK.' |
Within 2 minutes tidy.log has timestamped lines that match the approved dry run, and tidy.stamp exists |
| post_release | After the owner's docks-kit update: omp plugin list --json; omp config get task.agentIdleTtlMs; omp config get browser.idleCloseSec |
An npm row docks-kit-omp whose path resolves to ~/.docks-kit/omp-plugin; 120000; 600 |
Do not touch
SoT/.claude/andSoT/.codex/files.- Named Docker volumes, the owner database containers, the existing stash, and running omp processes.
- Deployed files under
~/.omp/agent/: no./docks-kit sync ompfrom the checkout before the release. - The vendored
unslopskill.
Open questions
Verification Results
Run on the linked branch, 2026-10-21, Linux workstation.
gate:bun run checkexit 0 on the reviewed head (83 test files passed, 1 skipped; 1210 tests passed, 10 skipped; dry-run goldens OK with 49 cases, mutation goldens OK with 73 cases).bun run check:generatedpasses. The mutation golden neededcli/test/lib/goldenExecution.ts readArgvLogto write the per-run home as<HOME>, becauseomp plugin linknames a path under that home.runtime_posix:bun run test:runtime:posixexit 0.sync_dry_run:./docks-kit sync omp --dry-runprints the four plugincplines and[dry-run] omp plugin link <home>/.docks-kit/omp-plugin; the kit plugin directory does not exist afterwards.scratch_smoke: the new directory has mode 700;removeprintedremoved <path>, exit 0, and the path is gone.scratch_refuse: the home directory and/tmpwere refused as outside the roots (exit 1); a symbolic link was refused (is a symbolic link), and after the code review round 1 repair<link>/is refused the same way, also for a link whose target is inside the scratch root; the link targets survived.tidy_dryandtidy_report: worktree, container, and branch counts were the same before and after (69, 11, 75). The report showed swap 7.2 GB,/tmp4.0 GB, Docker build cache 9.0 GB (5.2 GB reclaimable), Bun cache 2.8 GB, Trash 2.0 GB with the gsettings hint, and 27 merged branches. Two smoke findings were fixed: the Processes section listed omp's own worker broker (now excluded with the sweep's protected-process check, with a regression case), and the--repodry-run hint nameddocks-kit tidy --applyinstead ofdocks-kit tidy --repo <repo> --apply.guard_smoke:find /tmp/docks-smoke-none -deletewas blocked withBlocked by bash pattern: find *-delete*;printf x | xargs echoran and printedx.plugin_smoke:scratch_newreturned atool-smoke-*path,scratch_removeprintedremoved <path>, andskill://workspace-hygieneopened with# Workspace hygiene; notool-smoke-*directory remained.reminder_smoke: the session transcript containsYou created the scratch directoryandThese resources from this session still exist:; the final answer named the kept directory with its reason.cleanup_command:omp -e SoT/.omp/plugin -p '/cleanup'expanded the command (the transcript containsFollow the workspace-hygiene skill), ran the read-only report, and changed no file. This print-mode check replaced the interactive list check: an interactive attempt selected omp's built-in/cleanse, which started a repair run that was stopped before it changed any file.sweep_smoke: after the owner approved the 4-action dry run, the plugin sweep ran from one-psession and wrotetidy.logwithin seconds, andtidy.stampexists again. The log has 5 removals: the 4 approved worktrees (3 with their exited MySQL container) andevent-intake-skill, which crossed the 24 h idle threshold in the 25 minutes between the dry run and the sweep. It was merged into origin/main and clean, so its commits stay in the repository; ignored files in that checkout are gone.- Backlog, each group approved by the owner:
tidy --reporemoved 51 worktrees of backstage_wp_theme and 1 of site-manager (a fresh dry run before the apply showed no action outside the approved list); 21 agent-invented cache entries (about 8 GB) moved into the scratch root aslegacy-<name>, with three entries that hold git checkouts excluded; 7973 old/tmpentries removed and/tmpfell from 4.2 GB to 0.65 GB (one entry refused:EACCESinside a container bind mount); 2 exited containers removed;docker builder prune --filter until=168hfreed 0 B; pnpm store -1.3 GB, npm cache -0.4 GB, Bun cache cleared (2.8 GB), uv -13 MiB; 27 merged branches deleted; GNOME old trash and temp files removal enabled; Trash emptied. post_release: not run; it waits for the owner'sdocks-kit updateafter the release.- Repairs after code review round 1 were smoke-tested again:
scratch removerefused<link>/for a link inside the scratch root and kept its target; the plugin tools removedtool-smoke2-*andext-smoke2-*, and the transcript has the creation reminder;./docks-kit tidystill plans on this workstation (noprocess list unavailablenote). docks_issue: the docks session took the request. Plan plan-lifecycle: tear down what a plan created docks#83 and pull request plan-lifecycle: tear down what a plan created docks#84 (merged) add a Teardown section after Archive to the plan-lifecycle contract with the production rule; follow-up yolo: archive only on Merge now when the answer starts a new run docks#85 and docs(omp): record the home and root glob gap for plain file removal #86 (merged) fix the yolo landing step order. plan-lifecycle is not released yet (still 0.14.1).- Pull request feat(omp): agent resource hygiene with scratch, tidy, and the docks-kit-omp plugin; release 0.21.0 #90 checks:
check,lint, both goldens, andportableon macOS and Windows pass.portable (ubuntu-24.04)fails atbun auditon advisories published after main's last green run, in dev-only test dependencies (vitest 5.0.0 and vite 7.3.6), not in this change. By owner decision the fix ships first as its own pull request chore(deps): patch vitest and vite advisories (vitest 5.0.3, vite 7.3.7) #91 (vitest 5.0.3, vite 7.3.7); feat(omp): agent resource hygiene with scratch, tidy, and the docks-kit-omp plugin; release 0.21.0 #90 merges main after that.
Activity
DocksDocks commentedon Oct 21, 2026
Plan review round 1
Plan-review: repair
tidyandplugin; approved design Step 3adefaultTidyHost.processes- Returning[]outside Linux treats unavailable process information as proof that nothing uses a directory. Automatic sweeps remain enabled on macOS and Windows, both supported byos/targets.ts HOST_TARGETS. Active worktrees, scratch directories, and stacks can therefore qualify for removal. Implement supported-host in-use detection and fail closed when that information is unavailable.plugin; approved design Step 4a subprocess helper -execFilewith Windowsshell: truepasses unrestricted tool parameters through a command interpreter before scratch validation. This introduces command injection. Node’s documentation explicitly warns against this. Preserve literal arguments using executable resolution and the Windows encoding safeguards established byexec.ts spawnProcessandos/windows.ts commandShimInvocation.session_start- Separate stamp reads and writes permit concurrent omp processes to both observe an expired stamp and spawn sweeps. The later tidy lock does not enforce the 12-hour scheduling limit. Serialize the due-check and stamp update across processes, recheck after acquiring ownership, and let only the winner spawn.gate;AGENTS.mdTesting;package.jsonscripts - Acceptance omits the required POSIX runtime check and user-facing sync dry run.bun run checkincludes neither. Addbun run test:runtime:posixand./docks-kit sync omp --dry-run, preserving the prohibition on live pre-release deployment.DocksDocks commentedon Oct 21, 2026
Plan review round 2
Plan-review: repair
ps -o ...command excludes processes without controlling terminals, according to Apple’s manual. A successful but incomplete listing can permit cleanup of active directories. Useps -x -U <uid> -o pid=,ppid=,etime=,rss=,comm=,args=before joining working directories fromlsof.DocksDocks commentedon Oct 21, 2026
Plan review round 3
Plan-review: pass
None.
DocksDocks commentedon Oct 21, 2026
Plan review round 4
Plan-review: repair
ask. Docker documents that ordinary commands use the selected context orDOCKER_HOST/DOCKER_CONTEXT; local working-directory labels do not establish a local daemon. Prompt rules cannot constrain the background sweep. - Require tidy’s Docker operations to use a verified local endpoint, skipping remote or unknown endpoints. Keep local cleanup unchanged and route remote cleanup through the approved production rule.DocksDocks commentedon Oct 21, 2026
Plan review round 5
Plan-review: repair
npipe://does not guarantee locality. Windows named pipes support remote servers, and Docker passes the address to the pipe dialer. A remote pipe can therefore reach unattended container removal without the required production approval. Require a local-machine pipe path such asnpipe:////./pipe/...; reject remote-server pipe paths under the existing skip behavior.DocksDocks commentedon Oct 21, 2026
Plan review round 6
Plan-review: pass
None.
DocksDocks commentedon Oct 21, 2026
Code review round 1
Code-review: fixes-required
Standards
[HIGH] Security - cli/src/engine-native/scratch.ts:125 - The checked pathname is resolved again by
rmSync. If an agent-owned directory lies beneath an attacker-controlled/tmp/share, the attacker can replacesharewith a symlink after validation and redirect deletion outside the allowed roots, including into the home directory. - Anchor deletion to verified directory descriptors, or refuse paths whose ancestors another user can replace. Add an ancestor-swap regression test.[HIGH] Bug - cli/src/engine-native/tidy.ts:872 -
!existsSync(workingDir)treats permission and filesystem errors as proof that a directory is gone. An existing, inaccessible compose directory therefore schedulesdocker rm --force --volumes, bypassing idle and in-use checks and deleting its running containers and anonymous volumes. - Distinguish confirmed absence from probe failures; keep containers when directory existence is unknown.[MEDIUM] Bug - cli/src/engine-native/scratch.ts:113 - With
TMPDIR=/tmp/user-tmp, the removal roots include both/tmp/user-tmpand/tmp. Removing/tmp/user-tmpsucceeds because it is inside the latter, despite the documented refusal of roots themselves. - Reject a target equal to any allowed root before searching for a containing root.[MEDIUM] Bug - cli/src/engine-native/tidy.ts:1040 - Two processes can both identify the same stale lock, then one can unlink the other's newly acquired lock. Both enter
applyTidy, defeating sweep serialization. - Serialize stale-lock recovery across processes and release locks by ownership. Add a coordinated multi-process takeover test.[MEDIUM] Bug - cli/src/engine-native/tidy.ts:693 - Removing surrounding quotes does not decode Git's C-quoted paths. A recently modified filename such as
café.tsis consequently absent from activity measurements, allowing its development stack to be stopped as idle when admin files are old. - Read NUL-delimited porcelain output and parse its pathname fields correctly; test that recently modified quoted filenames keep their stacks running.[MEDIUM] Bug - SoT/.omp/plugin/extensions/hygiene.ts:651 - Docker global connection options are discarded. For
docker --context dev compose -p app up -d, the generated cleanup command and existence check use the default context instead, potentially targeting an unrelated same-named stack and leaving the created stack behind. - Preserve Docker connection targeting in resource identity, existence checks, and cleanup commands.[MEDIUM] Bug - SoT/.omp/plugin/extensions/hygiene.ts:718 - Subshell directory changes leak into subsequent detection. From
/w,(cd /a); git worktree add wtrecords/a/wt, although Git creates/w/wt; reminders can then name another registered worktree for removal. - Preserve directory scope across subshells and command substitutions, with regression tests.[LOW] Bug - SoT/.omp/plugin/extensions/hygiene.ts:1239 -
npm i -g xnever reaches the global-install detector becauseTRIGGERomits that spelling. Standalonedisownalso produces no entry because line 902 searches only arguments. Both miss the promised cleanup reminders. - Align filtering and command detection with the supported forms, and test throughtool_result.Spec
[HIGH] Spec - cli/src/engine-native/tidy.ts:806 - Plan omp: agent resource hygiene (scratch, tidy, docks-kit-omp plugin) #89 Research requires that
processes“returnsundefinedwhen the host cannot list processes with their working directories.” Instead, failed Linux cwd reads and missing macOS cwd records remain individualundefinedvalues, which this line silently drops. An active worktree or scratch directory can therefore be removed despite an incomplete safety probe. - Treat unresolved cwd information for a live process as an unavailable process list and retain every in-use-dependent candidate. Test partial probe failure, not only an entirely unavailable list.[MEDIUM] Spec - cli/src/engine-native/scratch.ts:107 - Acceptance
scratch_refuserequires both a “symlink operand and<link>/operand” to be refused. A trailing slash makeslstatSyncfollow a directory symlink; when its target is inside an allowed root, the target is deleted. The existing test covers only an outside target. - Check the final entry without trailing separators before resolving it, and test an inside-root symlink target survives.[MEDIUM] Spec - SoT/.omp/plugin/extensions/hygiene.ts:1086 - The Goal requires “at most once per 12 h” across processes. Two stale-claim contenders can unlink each other's replacement claims and both read the old stamp before either writes it, so both can start sweeps. Step 4's required concurrent test is instead two sequential calls at
cli/test/unit/ompKitPluginExtension.test.ts:266. - Serialize stale-claim takeover, release only the owned claim, and replace the sequential test with coordinated child processes.DocksDocks commentedon Oct 21, 2026
Plan review round 7
Plan-review: repair
tidy.ts orphanParents,planTidy- Skipping the unreadablesystemdrow removes the PID used to recognize its adopted orphans. Those processes then escape the required sweep. - PreservesystemdPID and name metadata. Apply the approved exception only to unreadable working-directory validation, not to subreaper discovery.TMPDIRbeneath an attacker-controlled directory passes those checks. Replacing an ancestor after validation can redirect path-basedrmSyncoutside the allowed roots, potentially into home. POSIX rename semantics establish that parent-directory permissions control replacement. - Require a trusted ancestor chain through the allowed root, including protection against ancestor replacement. Enforce this in shared removal logic used by both scratch and tidy.DocksDocks commentedon Oct 21, 2026
Plan review round 8
Plan-review: pass
None.
DocksDocks commentedon Oct 21, 2026
Code review round 2
Code-review: pass
Standards
[MEDIUM] Bug - cli/src/engine-native/scratch.ts:156 - Stripping trailing separators turns Windows
C:\into drive-relativeC:. If that drive's current directory lies inside an allowed root, removal targets that directory instead of refusing the requested filesystem root. - Preserve drive-root separators when trimming operands, and add a Windows regression case.[MEDIUM] Bug - cli/src/engine-native/tidy.ts:285 - [INFERENCE] When a process starts between the two concurrent
pssnapshots, it can appear infieldswithout appearing innames. This branch silently drops the live process, allowing its active worktree or scratch directory to pass the in-use check. - Resolve unmatched live PIDs, or return an unavailable process list; skip only confirmed exited processes.[MEDIUM] Bug - cli/src/engine-native/tidyLock.ts:114 - [INFERENCE] With both the lock and takeover mutex stale, two contenders can finish the text comparison before either unlinks. One can then unlink the other's replacement mutex, letting both replace the lock and enter
applyTidy. Owner tokens do not make compare-and-unlink atomic. - Make mutex recovery exclusive with an atomic locking primitive, and add a coordinated regression starting with both files stale.Spec
[MEDIUM] Spec - SoT/.omp/plugin/extensions/hygiene.ts:1310 - Plan omp: agent resource hygiene (scratch, tidy, docks-kit-omp plugin) #89 requires “at most once per 12 h.” [INFERENCE] Concurrent recovery of a stale takeover mutex repeats the original claim race: one contender can unlink another's fresh mutex, both can replace the claim, and both can read the old stamp before either writes it. - Make takeover-mutex recovery exclusive; test concurrent recovery with both the claim and mutex stale.
[MEDIUM] Spec - SoT/.omp/plugin/extensions/hygiene.ts:747 - Research requires preserving Docker targeting “in cleanup commands and existence checks.” Empty overrides are discarded. With inherited
DOCKER_CONTEXT=prod, a command prefixed withDOCKER_CONTEXT= DOCKER_HOST=unix:///dev.sockcreates resources on development, but the generated cleanup and stop check inheritprodagain. - Preserve empty environment overrides in resource identity, cleanup prefixes, and subprocess environments; test this inherited-context case.[MEDIUM] Spec - SoT/.omp/plugin/extensions/hygiene.ts:584 - Research requires keeping a
cd“inside a subshell … in that scope.” Pipeline commands still share the parent scope. From/w,cd /a | cat; git worktree add wtrecords/a/wt, although Bash creates/w/wt; reminders can name an unrelated worktree for removal. - Isolate pipeline and background-command directory state while preserving shared state for ordinary sequential commands and brace groups.feat(omp): agent resource hygiene with scratch, tidy, and the docks-k…