Skip to content

omp: agent resource hygiene (scratch, tidy, docks-kit-omp plugin) #89

Closed

Description

@DocksDocks

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) and tidy (safe leftover sweep plus a read-only --report of other disk and memory use).
  • bash.patterns denies the bulk-deletion forms that can empty the home directory (find -delete, find -exec rm, xargs rm, in every position).
  • docks-kit sync omp materializes and links a kit-owned omp plugin, docks-kit-omp, with an extension (native tools scratch_new, scratch_remove, tidy_report; creation reminders; a stop check; the automatic sweep), the workspace-hygiene skill, and the /cleanup command.
  • The current workstation backlog is reclaimed with owner confirmation, one ask per 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 remove or scratch_remove, both of which check paths in code.
  • An agent may run git worktree remove --force -- <path> once, after checks; never --force twice.
  • No new deny or prompt rules for nohup, setsid, disown, or docker; those are prompt rules, skill text, and reminders only.
  • At a main-session start the plugin runs docks-kit tidy --apply in 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.patterns are 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. omp CRITICAL_BASH_PATTERNS does not cover find, xargs, or docker.
  • Today find <home> -delete, cd <home> && find . -delete, find ~ -type f -exec rm {} +, and find ~ -type f | xargs rm match no deny rule and run under yolo. cli/test/unit/ompBashPatterns.test.ts lists xargs rm, find . -delete, and find . -name '*.tmp' -exec rm {} + as ALLOWED.
  • git worktree remove refuses the main worktree and unregistered paths; without --force it 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 with package.json omp.extensions loads those modules, and its sibling skills/, commands/, tools/, hooks/, rules/, prompts/, and .mcp.json are discovered. omp -e <dir> loads it for one run.
  • omp plugin list --json returns { npm, marketplace }; an npm row carries name, version, and path (the node_modules symlink).
  • pi.registerTool({ name, label, description, parameters, execute }) with pi.zod schemas; default loadMode discoverable, default approval exec. Events: tool_result (toolName, input, content, isError; may return additionalContext; fires in subagents), session_stop (main only; may return { continue: true, additionalContext }; stop_hook_active marks a continuation; cap 8), session_start (ctx.agent.kind main or sub). Module state is shared per omp process; handlers have a 30 s budget.
  • Current omp values: task.agentIdleTtlMs 420000, browser.idleCloseSec 1800.

Kit code:

  • CLI registration: cli/src/main.ts (Command.withSubcommands plus the manual root listing) and cli/src/argv.ts COMMANDS. Positional action words follow commands/toolchain.ts. engine.ts bail(message, code) reports usage errors; process.exitCode = 1 reports failures.
  • exec.ts spawnProcess returns { exitCode, stdout, stderr, error? } and ignores stderr by default; exec.ts p slash-joins paths. harnesses.ts engineHome resolves home. commands/omp.ts builds the overlay directory as p(home, ".cache", "docks-kit").
  • ompPlugins.ts syncPlugins reads the inventory once (installedPluginIdsFromCli, npm map of name to version), runs commands through runPluginCommand, records failures with recordFailure, and prints [dry-run] lines without running omp in a dry run. ompFileDeploy.ts ensureDirectory creates mode 0700 directories. payload.ts exports payloadText, payloadPaths(prefix), and payloadDisplayPath.
  • The SoT payload is an explicit list (cli/scripts/generate-sot-payload.ts TEXT_PAYLOAD_PATHS); cli/test/unit/payload.test.ts asserts every SoT/ file is listed. cli/tsconfig.json typechecks only the Claude bin/*.mjs SoT 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 with omp 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.agentIdleTtlMs 120000; browser.idleCloseSec 600.
  • tidy never passes --force to git worktree remove and never removes named volumes; docker rm --volumes removes anonymous volumes only.
  • Deployment waits for a release: the owner runs docks-kit update. Do not sync omp from the checkout while the global docks-kit lacks the new commands.
  • Process detection fails closed (plan review rounds 1 and 2). TidyHost.processes returns undefined when the host cannot list processes with their working directories. Linux reads ps -u <uid> -o pid=,ppid=,etimes=,rss=,comm=,args= (every process of the user, with or without a terminal) plus /proc/<pid>/cwd; macOS reads ps -x -U <uid> -o pid=,ppid=,etime=,rss=,comm=,args= (-x includes processes without a controlling terminal) plus lsof -nP -a -u <uid> -d cwd -Fpn; Windows and any failed probe give undefined. With undefined, planTidy creates no remove-worktree, stop-containers, stop-process, or remove-scratch action, keeps those candidates with the reason process list unavailable, and adds a note. Only remove-containers for a directory that is gone still runs.
  • No command interpreter in the extension (plan review round 1). Every extension subprocess uses execFile or spawn without shell. The extension resolves docks-kit on PATH itself; on Windows it accepts only a resolved .exe and refuses a .cmd or .bat shim with the not-on-PATH error, so a tool argument never reaches cmd.exe.
  • One sweep per interval across processes (plan review round 1). session_start claims tidy.stamp.claim with openSync(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 when sweepDue still holds, spawns docks-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 tidy and its automatic sweep stay as designed. Cleanup of a remote resource that an agent reaches through SSH or a provider CLI (for example wrangler, 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 an ask immediately before it, must be rare, and never runs in a subagent. A yolo invocation does not answer that ask. wrangler delete without --env acts on the top-level Worker, which is usually production. The rule goes into SoT/.omp/AGENTS.md, the workspace-hygiene skill, /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 with docker context inspect --format '{{.Endpoints.docker.Host}}', which honors DOCKER_HOST and DOCKER_CONTEXT. It accepts only a local-machine endpoint: unix:///<absolute socket path> (Linux and macOS) or npipe:////./pipe/<name> (Windows; the . server name is the local machine). A pipe with any other server name, such as npipe:////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 note docker 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 two ssh-agent. A process that exits during the probe is dropped from the list. A row named systemd, (sd-pam), ssh-agent, or gpg-agent stays in the list with an unknown working directory: it keeps its PID and name, so the subreaper rule still recognizes systemd, 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, which scratch 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 a TMPDIR inside /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 with wx. The extension keeps Docker daemon options (--context, --host, DOCKER_HOST, DOCKER_CONTEXT) in cleanup commands and existence checks, and keeps a cd inside 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/ and SoT/.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 omp from the checkout before the release.
  • The vendored unslop skill.

Open questions

Verification Results

Run on the linked branch, 2026-10-21, Linux workstation.

  • gate: bun run check exit 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:generated passes. The mutation golden needed cli/test/lib/goldenExecution.ts readArgvLog to write the per-run home as <HOME>, because omp plugin link names a path under that home.
  • 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 <home>/.docks-kit/omp-plugin; the kit plugin directory does not exist afterwards.
  • scratch_smoke: the new directory has mode 700; remove printed removed <path>, exit 0, and the path is gone.
  • scratch_refuse: the home directory and /tmp were 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_dry and tidy_report: worktree, container, and branch counts were the same before and after (69, 11, 75). The report showed swap 7.2 GB, /tmp 4.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 --repo dry-run hint named docks-kit tidy --apply instead of docks-kit tidy --repo <repo> --apply.
  • guard_smoke: find /tmp/docks-smoke-none -delete was blocked with Blocked by bash pattern: find *-delete*; printf x | xargs echo ran and printed x.
  • plugin_smoke: scratch_new returned a tool-smoke-* path, scratch_remove printed removed <path>, and skill://workspace-hygiene opened with # Workspace hygiene; no tool-smoke-* directory remained.
  • reminder_smoke: the session transcript contains You created the scratch directory and These 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 contains Follow 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 -p session and wrote tidy.log within seconds, and tidy.stamp exists again. The log has 5 removals: the 4 approved worktrees (3 with their exited MySQL container) and event-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 --repo removed 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 as legacy-<name>, with three entries that hold git checkouts excluded; 7973 old /tmp entries removed and /tmp fell from 4.2 GB to 0.65 GB (one entry refused: EACCES inside a container bind mount); 2 exited containers removed; docker builder prune --filter until=168h freed 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's docks-kit update after the release.
  • Repairs after code review round 1 were smoke-tested again: scratch remove refused <link>/ for a link inside the scratch root and kept its target; the plugin tools removed tool-smoke2-* and ext-smoke2-*, and the transcript has the creation reminder; ./docks-kit tidy still plans on this workstation (no process list unavailable note).
  • 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, and portable on macOS and Windows pass. portable (ubuntu-24.04) fails at bun audit on 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

  1. self-assigned this
    on Oct 21, 2026
  2. DocksDocks commented on Oct 21, 2026

    @DocksDocks
    OwnerAuthor

    Plan review round 1

    Plan-review: repair

    • security_risk - Steps tidy and plugin; approved design Step 3a defaultTidyHost.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 by os/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.
    • security_risk - Step plugin; approved design Step 4a subprocess helper - execFile with Windows shell: true passes 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 by exec.ts spawnProcess and os/windows.ts commandShimInvocation.
    • goal_fit - Goal’s 12-hour limit; approved design Step 4a 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.
    • goal_fit - Acceptance gate; AGENTS.md Testing; package.json scripts - Acceptance omits the required POSIX runtime check and user-facing sync dry run. bun run check includes neither. Add bun run test:runtime:posix and ./docks-kit sync omp --dry-run, preserving the prohibition on live pre-release deployment.
  3. DocksDocks commented on Oct 21, 2026

    @DocksDocks
    OwnerAuthor

    Plan review round 2

    Plan-review: repair

    • research_gap - Research, “Process detection fails closed”; Step 3 - The macOS ps -o ... command excludes processes without controlling terminals, according to Apple’s manual. A successful but incomplete listing can permit cleanup of active directories. Use ps -x -U <uid> -o pid=,ppid=,etime=,rss=,comm=,args= before joining working directories from lsof.
  4. DocksDocks commented on Oct 21, 2026

    @DocksDocks
    OwnerAuthor

    Plan review round 3

    Plan-review: pass

    None.

  5. DocksDocks commented on Oct 21, 2026

    @DocksDocks
    OwnerAuthor

    Plan review round 4

    Plan-review: repair

    • security_risk - Research “Production rule”; Steps 3–4 - The unattended sweep can still target remote production Docker resources without an ask. Docker documents that ordinary commands use the selected context or DOCKER_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.
  6. DocksDocks commented on Oct 21, 2026

    @DocksDocks
    OwnerAuthor

    Plan review round 5

    Plan-review: repair

    • security_risk - Research, “Local Docker endpoint only”; Steps row 3 - Round 4 remains partially unresolved: 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 as npipe:////./pipe/...; reject remote-server pipe paths under the existing skip behavior.
  7. DocksDocks commented on Oct 21, 2026

    @DocksDocks
    OwnerAuthor

    Plan review round 6

    Plan-review: pass

    None.

  8. DocksDocks commented on Oct 21, 2026

    @DocksDocks
    OwnerAuthor

    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 replace share with 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 schedules docker 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-tmp and /tmp. Removing /tmp/user-tmp succeeds 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é.ts is 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 wt records /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 x never reaches the global-install detector because TRIGGER omits that spelling. Standalone disown also 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 through tool_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 “returns undefined when the host cannot list processes with their working directories.” Instead, failed Linux cwd reads and missing macOS cwd records remain individual undefined values, 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_refuse requires both a “symlink operand and <link>/ operand” to be refused. A trailing slash makes lstatSync follow 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.

  9. DocksDocks commented on Oct 21, 2026

    @DocksDocks
    OwnerAuthor

    Plan review round 7

    Plan-review: repair

    • goal_fit - Research, “Unreadable process directories”; tidy.ts orphanParents, planTidy - Skipping the unreadable systemd row removes the PID used to recognize its adopted orphans. Those processes then escape the required sweep. - Preserve systemd PID and name metadata. Apply the approved exception only to unreadable working-directory validation, not to subreaper discovery.
    • security_risk - Research, “Code review round 1 repairs”; Steps 2–3 - Checking root permissions and ancestors only below that root leaves its own ancestors unchecked. [INFERENCE] A private TMPDIR beneath an attacker-controlled directory passes those checks. Replacing an ancestor after validation can redirect path-based rmSync outside 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.
  10. DocksDocks commented on Oct 21, 2026

    @DocksDocks
    OwnerAuthor

    Plan review round 8

    Plan-review: pass

    None.

  11. DocksDocks commented on Oct 21, 2026

    @DocksDocks
    OwnerAuthor

    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-relative C:. 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 ps snapshots, it can appear in fields without appearing in names. 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 with DOCKER_CONTEXT= DOCKER_HOST=unix:///dev.sock creates resources on development, but the generated cleanup and stop check inherit prod again. - 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 wt records /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.

  12. added a commit that references this issue on Oct 21, 2026
    4b7adfe
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions