Skip to content

Set GOTMPDIR per task so interrupted Go builds don't leak /tmp#36

Open
e-jung wants to merge 2 commits into
kunchenguid:mainfrom
e-jung:set-gotmpdir-per-task-c7
Open

Set GOTMPDIR per task so interrupted Go builds don't leak /tmp#36
e-jung wants to merge 2 commits into
kunchenguid:mainfrom
e-jung:set-gotmpdir-per-task-c7

Conversation

@e-jung

@e-jung e-jung commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Problem

Go's GOTMPDIR is unset, so every go build / go test in a crewmate pane creates numbered temp dirs (/tmp/go-build*). Go cleans these on a clean exit but leaves them behind when a build is interrupted — signal, timeout, OOM, or full disk. Over time these accumulate and fill the disk on the firstmate VPS.

Fix

Give each task its own Go temp dir that teardown cleans deterministically.

  • bin/fm-spawn.sh creates a per-task temp root /tmp/fm-<id>/ with Go's build temp nested at /tmp/fm-<id>/gotmp/ (Go won't mkdir GOTMPDIR, so it must exist first). It export GOTMPDIR=... into the crewmate's pane shell so the agent and every child process inherit it, and records tasktmp= in the task's meta.
  • bin/fm-teardown.sh reads tasktmp= from meta and rm -rfs the whole root on cleanup, so interrupted builds can never outlive the task.

GOTMPDIR (not TMPDIR) is the targeted knob — TMPDIR is too broad (it redirects every program's temp, not just Go's). The nested root is extensible: other per-task temp can live under /tmp/fm-<id>/ later, and teardown cleans one deterministic path.

Backward compatibility

Tasks spawned before this change have no tasktmp= line in their meta. Teardown reads the value with an empty-tolerant grep and treats the empty string as a no-op ([ -n "$TASK_TMP" ] && rm -rf ...), so pre-existing tasks tear down exactly as before.

Safety net

The daily ~/.local/bin/fm-disk-cleanup.sh cron (rm -rf /tmp/go-build* ...) remains in place and sweeps any pre-fix stray dirs or anything missed in edge cases.

Tests

tests/fm-gotmp.test.sh (TAP style) covers:

  1. fm-spawn creates gotmp/ and records tasktmp= in meta (structural contract + behavioral simulation).
  2. fm-teardown removes the dir pointed to by tasktmp= (runs the real teardown via symlink against a fake root — exercises actual code, sources nothing).
  3. fm-teardown skips gracefully when tasktmp= is absent (backward compat).
  4. fm-teardown skips gracefully when tasktmp= points to a nonexistent dir.

All existing tests (fm-spawn-batch, fm-wake-queue) still pass.


🤖 AI disclosure: Human-reviewed

e-jung added 2 commits June 22, 2026 02:34
… /tmp

Go's GOTMPDIR is unset, so every go build/test creates numbered /tmp/go-build*
dirs. Go cleans them on a clean exit but LEAVES THEM when interrupted (signal,
timeout, OOM, full disk), accumulating and filling the disk over time.

Give each task its own temp root at /tmp/fm-<id>/ with Go's build temp nested at
gotmp/. fm-spawn creates the dir (Go won't mkdir GOTMPDIR), exports GOTMPDIR into
the crewmate pane so the agent and child processes inherit it, and records
tasktmp= in meta. fm-teardown reads tasktmp= and removes the whole root on
cleanup, deterministically.

GOTMPDIR (not TMPDIR) is the targeted knob: TMPDIR is too broad (affects every
program's temp). The nested root is extensible: other per-task temp can live
under /tmp/fm-<id>/ later.

Backward compat: tasks spawned before this change have no tasktmp= in meta;
teardown tolerates the empty value as a no-op. The daily fm-disk-cleanup.sh cron
remains a safety net for any pre-fix stray dirs.
The structural grep -F assertions deliberately match literal $TASK_TMP in the
fm-spawn source; add per-line shellcheck disable=SC2016 (the codebase's existing
pattern, e.g. bin/fm-spawn.sh) so CI lint passes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant