Skip to content

fix: reclaim non-writable /tmp/gh-aw/sandbox before AWF writeConfigs() to prevent EACCES#42400

Open
Copilot wants to merge 2 commits into
mainfrom
copilot/aw-failures-reclaim-rootless-sandbox
Open

fix: reclaim non-writable /tmp/gh-aw/sandbox before AWF writeConfigs() to prevent EACCES#42400
Copilot wants to merge 2 commits into
mainfrom
copilot/aw-failures-reclaim-rootless-sandbox

Conversation

Copilot AI commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Rootless containers on GitHub-hosted runners can leave /tmp/gh-aw/sandbox owned by root. AWF's writeConfigs() then dies with EACCES: mkdir /tmp/gh-aw/sandbox/firewall/logs before the agent is ever invoked — and the chmod -R a+rX fallback inside AWF fails for the same reason, making the error unrecoverable. This manifests as a per-runner ownership race: identical runs succeed or fail depending on whether a prior rootless job ran on the same runner.

Changes

  • create_gh_aw_tmp_dir.sh — added a pre-flight check before mkdir: if /tmp/gh-aw/sandbox exists but is not writable by the current user, remove it with sudo rm -rf (falling back to plain rm -rf) so the subsequent mkdir -p creates a fresh, uid-owned tree before AWF starts

    if [ -d "${sandbox_dir}" ] && ! [ -w "${sandbox_dir}" ]; then
      echo "[WARN] ... reclaiming before AWF starts..."
      sudo rm -rf "${sandbox_dir}" 2>/dev/null || rm -rf "${sandbox_dir}" 2>/dev/null || ...
    fi

    Uses [ -w ] rather than UID comparison — portable across Linux/macOS and directly tests the condition that causes EACCES.

  • create_gh_aw_tmp_dir_test.sh — new test covering: syntax validity, normal creation, user-owned sandbox preservation (no removal), and simulated non-writable sandbox reclaim (fake sudo records arguments and mimics root privilege).

…) to prevent EACCES

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix EACCES error in Smoke CI startup fix: reclaim non-writable /tmp/gh-aw/sandbox before AWF writeConfigs() to prevent EACCES Jun 30, 2026
Copilot AI requested a review from pelikhan June 30, 2026 02:45
@pelikhan pelikhan marked this pull request as ready for review June 30, 2026 03:41
Copilot AI review requested due to automatic review settings June 30, 2026 03:41

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens gh-aw’s runner setup by proactively reclaiming a stale, non-writable /tmp/gh-aw/sandbox (often left root-owned by prior rootless container jobs) so AWF writeConfigs() doesn’t fail early with EACCES during sandbox config/log directory creation.

Changes:

  • Add preflight detection and reclamation of /tmp/gh-aw/sandbox when it exists but is not writable, using sudo rm -rf with a fallback to rm -rf.
  • Add a shell test script validating normal creation, preservation of user-owned sandboxes, and simulated reclaim behavior.
  • Add a changeset documenting the patch release.
Show a summary per file
File Description
actions/setup/sh/create_gh_aw_tmp_dir.sh Adds sandbox reclamation logic prior to creating gh-aw temp directories.
actions/setup/sh/create_gh_aw_tmp_dir_test.sh Adds a dedicated test script to validate the new sandbox reclaim behavior.
.changeset/patch-reclaim-sandbox-rootless-eacces.md Documents the patch release rationale and behavior change.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 3/3 changed files
  • Comments generated: 3
  • Review effort level: Low

Comment on lines 1 to +3
#!/usr/bin/env bash
set +o histexpand

Comment on lines +17 to +20
else
echo "[WARN] Failed to remove ${sandbox_dir}; AWF writeConfigs() may fail with EACCES" >&2
fi
fi
Comment on lines +12 to +14
TESTS_PASSED=0
TESTS_FAILED=0

@github-actions

github-actions Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

@github-actions

github-actions Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

No ADR enforcement needed: PR does not have the 'implementation' label and has ≤100 new lines of code in business logic directories.

@github-actions

github-actions Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

@github-actions

github-actions Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: fix: reclaim non-writable /tmp/gh-aw/sandbox

The root-cause analysis is correct and the -w portability argument is sound. The fix targets the right point in the lifecycle (before AWF starts). Three issues block a clean merge:

Blocking

  1. Silent false-success after failed reclaim (line 23, create_gh_aw_tmp_dir.sh) — if both sudo rm -rf and rm -rf fail, a warning is printed but the script continues. Without set -e, the subsequent mkdir -p /tmp/gh-aw/sandbox/agent/logs fails silently, and the script exits 0 — the exact failure mode this PR is fixing is still possible in that path. Fix: exit 1 in the else branch (or add set -e earlier).

Non-blocking (suggested)

  1. sudo stderr suppressed (line 13) — 2>/dev/null hides all sudo errors, making post-hoc debugging harder on flaky runners.

  2. eval in assert() (create_gh_aw_tmp_dir_test.sh line 63) — interpolating paths into eval'd strings is fragile if mktemp -d returns a path with special characters; prefer varargs-style assert.

What looks good
  • Portable [ -w ] check instead of UID comparison
  • Correct sudo-then-plain-rm fallback chain (the parent /tmp/gh-aw being writable makes plain rm -rf viable without sudo)
  • Test coverage for all three scenarios: fresh creation, user-owned preservation, and non-writable reclaim
  • Changeset file included

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · 40.6 AIC · ⌖ 7.14 AIC · ⊞ 4.9K

fi

mkdir -p /tmp/gh-aw/agent
mkdir -p /tmp/gh-aw/sandbox/agent/logs

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Silent false-success on unrecoverable removal failure.

If both sudo rm -rf and rm -rf fail (the else branch on line 17–18), a [WARN] is emitted but the script keeps running. Because this script has no set -e, the mkdir -p here fails with EACCES and exits silently — the script then reaches the echo on line 24 and exits 0 with a misleading success message. This recreates the exact outcome the PR is trying to prevent.

Consider either exiting early in the else branch, or adding set -e so the failing mkdir propagates:

else
  echo "[WARN] Failed to remove ${sandbox_dir}; cannot continue" >&2
  exit 1
fi

@copilot please address this.

sandbox_dir="/tmp/gh-aw/sandbox"
if [ -d "${sandbox_dir}" ] && ! [ -w "${sandbox_dir}" ]; then
echo "[WARN] ${sandbox_dir} is not writable by the current user (uid $(id -u)); reclaiming before AWF starts..."
if sudo rm -rf "${sandbox_dir}" 2>/dev/null; then

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stderr suppressed on sudo may hide root-cause diagnostics.

sudo rm -rf "${sandbox_dir}" 2>/dev/null discards all stderr from sudo (permission denied, sudo not configured, PAM errors, etc.). If the removal fails, the caller only knows it failed — not why. Consider capturing stderr into the fallback warning so operators can diagnose the environment:

SUDO_ERR="$(sudo rm -rf "${sandbox_dir}" 2>&1)" && echo "Removed ... via sudo" || {
  [[ -n "$SUDO_ERR" ]] && echo "  sudo error: $SUDO_ERR" >&2
  rm -rf "${sandbox_dir}" 2>/dev/null || ...
}

Not blocking, but worth improving for debuggability in ephemeral runner environments.

@copilot please address this.

assert "no WARN about reclaiming in output" "! echo '${OUTPUT}' | grep -q 'reclaiming'"
rm -f "${MARKER_FILE}"
echo ""

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

eval in assert() is fragile with paths that contain special characters.

Conditions are constructed by interpolating shell variables (e.g. ${SUDO_ARGS_FILE}, ${MARKER_FILE}) into strings and then eval-ing them. If a temp-dir path from mktemp -d happens to contain whitespace or glob characters, assertions can silently mismatch or expand unexpectedly.

Prefer using a function that accepts a command and its arguments as separate parameters — this avoids eval entirely:

assert() {
  local name="$1"; shift
  if "$@" 2>/dev/null; then
    echo "${name}"
    TESTS_PASSED=$((TESTS_PASSED + 1))
  else
    echo "${name}"
    TESTS_FAILED=$((TESTS_FAILED + 1))
  fi
}
# Call: assert "marker gone" test "!" "-f" "${MARKER_FILE}"

@copilot please address this.

@github-actions

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

⚠️ Shell tests only — formal TQS scoring does not apply (shell is not in Go/JS scope).

Analyzed 4 test scenario(s) in create_gh_aw_tmp_dir_test.sh (bash). The TQS scoring formula applies to Go (*_test.go) and JavaScript (*.test.cjs, *.test.js) tests; shell tests are reviewed qualitatively per the "other languages" policy.

📊 Metrics & Test Classification (4 tests analyzed)
Metric Value
New/modified tests analyzed 4 (shell)
✅ Design tests (behavioral contracts) 3 (75%)
⚠️ Implementation tests (low value) 1 (25%)
Tests with error/edge cases 2 (50%)
Duplicate test clusters 0
Test inflation (shell, informational) YES — 115 added / 19 added ≈ 6:1
🚨 Coding-guideline violations 0 — no Go/JS tests; build-tag and mock checks not applicable
Test File Classification Issues Detected
1: Script syntax is valid create_gh_aw_tmp_dir_test.sh:25 ⚠️ Implementation Verifies bash -n only — no behavioral assertion
2: Creates expected directories from scratch create_gh_aw_tmp_dir_test.sh:31 ✅ Design Happy-path only; acceptable for this scenario
3: Preserves sandbox owned by current user create_gh_aw_tmp_dir_test.sh:44 ✅ Design Critical negative invariant — edge case covered ✓
4: Reclaims non-writable sandbox (simulated) create_gh_aw_tmp_dir_test.sh:65 ✅ Design Core fix scenario; fake sudo simulates privilege path ✓

Go: 0 (*_test.go); JavaScript: 0 (*.test.cjs, *.test.js). Shell: 1 file detected, qualitatively reviewed, not formally scored.

⚠️ Flagged Tests — Low Priority (1 note)

1: Script syntax is valid (create_gh_aw_tmp_dir_test.sh:25) — ⚠️ Implementation: bash -n is a static syntax check, not a behavioral assertion. If deleted, no meaningful regression would escape — syntax errors are caught by the tests that actually execute the script. Low-priority note; this is a common and harmless practice in shell test suites.

Verdict

Check passed. No Go or JavaScript tests were added — the formal TQS scoring formula and guideline checks (Go mock libraries, missing build tags) do not apply. Shell tests qualitatively cover the key behavioral contracts: fresh directory creation (Test 2), the "do not remove user-owned sandbox" negative invariant (Test 3), and the core EACCES reclaim path via fake-sudo simulation (Test 4). Test 4 is particularly well-designed — the fake sudo correctly mimics root privilege without requiring actual root access. Qualitative equivalent: ~65/100 ⚠️ Acceptable (6:1 line ratio is expected for a new comprehensive test suite covering a small targeted fix).

References:

🧪 Test quality analysis by Test Quality Sentinel · 60.8 AIC · ⌖ 14.7 AIC · ⊞ 6.7K ·
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Test Quality Sentinel: ~65/100 (Acceptable, shell-only). No Go or JavaScript tests were added — the formal implementation-test ratio check and guideline checks (Go mock libraries, missing build tags) do not apply. 0% implementation tests in formal scope (threshold: 30%). Shell tests cover all key behavioral contracts including the critical non-writable sandbox reclaim scenario.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking observations on the reclaim logic and test harness

The approach (writability check → sudo reclaim → recreate) is sound for the described failure mode. Three additional issues not yet covered by the existing review:


  • Line 23 hardcodes /tmp/gh-aw/sandbox/agent/logs rather than using the sandbox_dir variable defined on line 10. Minor maintenance inconsistency today, silent bug if the variable is ever made configurable.

  • The assert() helper uses eval "${condition}", with ${OUTPUT} expanded at the call site into a single-quoted string. A future warning message containing ' would break the eval silently (error swallowed by 2>/dev/null), flipping a test's result without any signal.
  • Tests 2–4 operate directly on the real /tmp/gh-aw/sandbox path. If the test suite is run on a self-hosted or shared runner while an AWF job is active, the chmod 555, rm -rf, and mkdir -p calls will race against live AWF I/O. Redirecting via an env var (GH_AW_TMP_BASE) would isolate the tests completely.

None of these block the merge on their own, but the test isolation issue becomes a real problem once this script is exercised in CI environments where AWF jobs co-exist.

🔎 Code quality review by PR Code Quality Reviewer · 85.1 AIC · ⌖ 7.85 AIC · ⊞ 1.6K
Comment /review to run again

fi

mkdir -p /tmp/gh-aw/agent
mkdir -p /tmp/gh-aw/sandbox/agent/logs

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sandbox_dir variable defined but not used here — hardcoded path will silently drift if the variable ever changes.

💡 Suggested fix

Line 10 defines sandbox_dir="/tmp/gh-aw/sandbox" as a single source of truth, but line 23 hardcodes the same literal path:

# current
mkdir -p /tmp/gh-aw/sandbox/agent/logs

# fix
mkdir -p "${sandbox_dir}/agent/logs"

If sandbox_dir is ever updated (e.g. made configurable), the reclaim block picks up the new value but this mkdir still targets the old path — silently recreating the stale root-owned tree that this PR is trying to fix. Use the variable consistently.

assert "script exits 0" "[ '${EXIT_CODE}' = '0' ]"
assert "/tmp/gh-aw/agent directory created" "[ -d /tmp/gh-aw/agent ]"
assert "/tmp/gh-aw/sandbox/agent/logs directory created" "[ -d /tmp/gh-aw/sandbox/agent/logs ]"
assert "output mentions created directory" "echo '${OUTPUT}' | grep -q 'Created /tmp/gh-aw/agent directory'"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests mutate the real /tmp/gh-aw/sandbox path without isolation — running this test suite while an AWF job is live will corrupt the active sandbox.

💡 Suggested fix

Tests 2–4 all operate directly on /tmp/gh-aw/sandbox. If these tests are ever run on a shared or self-hosted runner that has a live AWF session, the chmod 555, rm -rf, and mkdir -p calls will race against AWF writes.

The script should accept a configurable base path so the tests can redirect to a throw-away directory:

# In the script under test
GH_AW_TMP_BASE="${GH_AW_TMP_BASE:-/tmp/gh-aw}"
sandbox_dir="${GH_AW_TMP_BASE}/sandbox"
# In the test setup
export GH_AW_TMP_BASE="$(mktemp -d)"
trap 'chmod -R u+w "${GH_AW_TMP_BASE}" 2>/dev/null; rm -rf "${GH_AW_TMP_BASE}"' EXIT
OUTPUT="$(bash "${SCRIPT}" 2>&1)"

This also eliminates the need for a separate EXIT trap because the mktemp dir is always cleaned up.

assert() {
local name="$1"
local condition="$2"
if eval "${condition}" 2>/dev/null; then

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

eval "${condition}" will silently misfire if a captured output variable contains single quotes or shell metacharacters.

💡 Suggested fix

assert receives a condition string like "echo '${OUTPUT}' | grep -q '...'". The caller expands ${OUTPUT} at the call site, embedding it inside single quotes in the eval'd string. If the script output ever contains a ' character (e.g. from a path like can't write, or a future warning message), the heredoc-style quoting breaks and eval either throws a syntax error (silenced by 2>/dev/null) or mis-parses the pipeline — causing a test to pass or fail spuriously with no indication of why.

Replace the generic eval-based helper with direct assertions on pre-captured variables:

# Instead of:
assert "output mentions created directory" "echo '${OUTPUT}' | grep -q 'Created'"

# Prefer:
if echo "${OUTPUT}" | grep -q 'Created /tmp/gh-aw/agent directory'; then
  echo "  ✓ output mentions created directory"
  TESTS_PASSED=$((TESTS_PASSED + 1))
else
  echo "  ✗ output mentions created directory"
  TESTS_FAILED=$((TESTS_FAILED + 1))
fi

Or keep the helper but have callers pass a pre-evaluated boolean (exit code) rather than a string to eval.

@github-actions

Copy link
Copy Markdown
Contributor

Review Summary — /diagnosing-bugs + /tdd 🧠

REQUEST_CHANGES on two correctness gaps. The fix direction is solid and the test harness is a great addition — a few things to address before merging.

🔴 Blocking (2)
  1. Incomplete fix scope (create_gh_aw_tmp_dir.sh line 11): The guard only reclaims /tmp/gh-aw/sandbox. The parent /tmp/gh-aw can be root-owned for the same reason, and mkdir -p /tmp/gh-aw/agent would hit EACCES just the same. Extend the writability check to the parent directory.

  2. Missing failure-path test (create_gh_aw_tmp_dir_test.sh line 108): The else branch — when both sudo and rm -rf fail — exits 0 with a WARN and lets AWF run into the original EACCES. Add Test 5 with fake sudo and rm that both exit 1 to pin that behaviour.

🟡 Suggested (4)
  • Line 13 — capture sudo's stderr and thread it through the WARN message for faster triage
  • Line 22 — derive mkdir paths from a gh_aw_dir variable to stay DRY with the sandbox guard
  • Test line 18 — replace eval "${condition}" with typed assertion helpers (assert_output_contains, assert_file_exists)
  • Test line 36 — add trap cleanup EXIT and a pre-test rm -rf so re-runs are idempotent

@copilot please address the review comments above.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · 97.2 AIC · ⌖ 9.54 AIC · ⊞ 6.6K ·
Comment /matt to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skills-Based Review 🧠

Applied /diagnosing-bugs and /tdd — requesting changes on two correctness gaps before merging.

📋 Key Themes & Highlights

Blocking Issues

  • Incomplete fix scope: The guard reclaims /tmp/gh-aw/sandbox but not its parent /tmp/gh-aw. If the parent is also root-owned (a plausible rootless residue outcome), mkdir -p /tmp/gh-aw/agent hits EACCES anyway — the bug is not fully fixed.
  • Missing failure-path test: The test suite covers the sudo-success branch (Test 4) but has no test for the else branch (both sudo and rm -rf fail). This is the path that produces the silent exits 0, WARN to stderr behaviour — the most dangerous outcome to leave unguarded.

Suggested Improvements

  • Capture and surface sudo's stderr in the fallback WARN message (comment on line 13) — currently the only visible error is a generic string that gives no clue whether sudo is missing, unconfigured, or the dir is on a read-only mount.
  • Add trap cleanup EXIT in the test harness (comment on line 36) — tests re-run non-idempotently against real /tmp/gh-aw paths.
  • Replace eval "${condition}" in assert() with type-specific helpers (comment on line 18) — the current form breaks silently when output contains single quotes.
  • Introduce a gh_aw_dir variable and derive sandbox_dir from it (comment on line 22) — removes a silent path-drift footgun.

Positive Highlights

  • ✅ Using [ -w ] over UID comparison is the right portability call (works across Linux and macOS)
  • ✅ sudo → plain rm fallback chain is a sound defensive strategy
  • ✅ New test file with 4 scenarios, including a fake-sudo harness, is well-structured and goes well beyond syntax checking
  • ✅ Clear commit message and PR description with exact error reproduction details

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · 97.2 AIC · ⌖ 9.54 AIC · ⊞ 6.6K
Comment /matt to run again

assert "non-writable sandbox was removed (marker gone)" "[ ! -f '${MARKER_FILE}' ]"

rm -rf "${FAKE_BIN}"
echo ""

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/tdd] The else branch (when both sudo and plain rm -rf fail) has no test. This is the most safety-critical path — the script emits a WARN and exits 0, silently allowing AWF to proceed and hit the original EACCES. Without coverage here, a future change to the warning text or exit logic has no regression guard.

💡 Suggested Test 5 skeleton
echo "Test 5: WARN when both sudo and rm -rf fail (fallthrough path)"
mkdir -p /tmp/gh-aw/sandbox
chmod 555 /tmp/gh-aw/sandbox

FAKE_BIN3="$(mktemp -d)"
printf '#!/usr/bin/env bash\nexit 1\n' > "${FAKE_BIN3}/sudo"
printf '#!/usr/bin/env bash\nexit 1\n' > "${FAKE_BIN3}/rm"
chmod +x "${FAKE_BIN3}/sudo" "${FAKE_BIN3}/rm"

set +e
OUTPUT="$(PATH="${FAKE_BIN3}:${PATH}" bash "${SCRIPT}" 2>&1)"
EXIT_CODE=$?
set -e

assert "script exits 0 on full fallthrough" "[ '${EXIT_CODE}' = '0' ]"
assert "WARN about failed removal emitted" "echo '${OUTPUT}' | grep -q 'Failed to remove'"

chmod 755 /tmp/gh-aw/sandbox && rm -rf "${FAKE_BIN3}"

Covering this path ensures the diagnostic warning message is never accidentally dropped.

@copilot please address this.

sandbox_dir="/tmp/gh-aw/sandbox"
if [ -d "${sandbox_dir}" ] && ! [ -w "${sandbox_dir}" ]; then
echo "[WARN] ${sandbox_dir} is not writable by the current user (uid $(id -u)); reclaiming before AWF starts..."
if sudo rm -rf "${sandbox_dir}" 2>/dev/null; then

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/diagnosing-bugs] sudo rm -rf ... 2>/dev/null discards all diagnostic context. If sudo is unconfigured (no NOPASSWD rule, passwordless sudo not available on the runner), the failure is invisible — only the generic WARN on the else branch surfaces, with no indication of why sudo failed.

💡 Suggestion: capture and surface sudo's stderr
if sudo_err=$(sudo rm -rf "${sandbox_dir}" 2>&1); then
  echo "Removed stale non-writable ${sandbox_dir} via sudo"
elif rm -rf "${sandbox_dir}" 2>/dev/null; then
  echo "Removed stale ${sandbox_dir}"
else
  echo "[WARN] Failed to remove ${sandbox_dir}; AWF writeConfigs() may fail with EACCES${sudo_err:+ (sudo: ${sudo_err})}" >&2
fi

This keeps the happy path clean but threads sudo's error message through to the final WARN, making production triage immediate rather than requiring another run with manual debugging.

@copilot please address this.

# Permission denied for the same reason, so the only reliable fix is to remove and recreate the
# tree here, before AWF starts.
sandbox_dir="/tmp/gh-aw/sandbox"
if [ -d "${sandbox_dir}" ] && ! [ -w "${sandbox_dir}" ]; then

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/diagnosing-bugs] The guard only checks $sandbox_dir (/tmp/gh-aw/sandbox), but the same rootless-container residue can affect the parent /tmp/gh-aw. If /tmp/gh-aw itself is root-owned, mkdir -p /tmp/gh-aw/agent on line 22 will fail with EACCES for exactly the same reason, bypassing this fix entirely.

💡 Suggestion: extend the guard to the parent
gh_aw_dir="/tmp/gh-aw"
sandbox_dir="${gh_aw_dir}/sandbox"

for dir in "${gh_aw_dir}" "${sandbox_dir}"; do
  if [ -d "${dir}" ] && ! [ -w "${dir}" ]; then
    echo "[WARN] ${dir} is not writable by uid $(id -u); reclaiming..."
    if sudo_err=$(sudo rm -rf "${dir}" 2>&1); then
      echo "Removed ${dir} via sudo"
    elif rm -rf "${dir}" 2>/dev/null; then
      echo "Removed ${dir}"
    else
      echo "[WARN] Failed to remove ${dir}" >&2
    fi
  fi
done

Alternatively, just add an identical guard block for gh_aw_dir before the sandbox one. The PR description focuses on the sandbox leaf, but the parent is exposed to the same rootless residue.

@copilot please address this.

assert() {
local name="$1"
local condition="$2"
if eval "${condition}" 2>/dev/null; then

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/tdd] eval "${condition}" is fragile: if any assertion string contains a single quote (from script output, a path, or an error message), the eval'd condition breaks silently — the assertion counts as failed but gives no diagnostic hint.

💡 Suggestion: use a dedicated output assertion helper
# Instead of: assert "msg" "echo '${OUTPUT}' | grep -q 'reclaiming'"
assert_output_contains() {
  local name="$1" needle="$2"
  if printf '%s' "${OUTPUT}" | grep -qF "${needle}"; then
    echo "${name}"
    TESTS_PASSED=$((TESTS_PASSED + 1))
  else
    echo "${name}: expected output to contain: ${needle}"
    TESTS_FAILED=$((TESTS_FAILED + 1))
  fi
}

assert_file_exists() {
  local name="$1" path="$2"
  if [ -e "${path}" ]; then ...

Keeping file/output assertions separate from the generic eval-based helper eliminates the quoting hazard and also improves failure messages (the needle is printed, not just "assertion failed").

@copilot please address this.

echo ""

# ── Test 2: Creates expected directories when they don't exist ───────────────
echo "Test 2: Creates expected directories from scratch"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/tdd] Test 2 says "from scratch" but doesn't ensure /tmp/gh-aw is clean first. If run after Test 1 (which runs the script) or in an already-provisioned environment, /tmp/gh-aw/sandbox/agent/logs already exists — the test passes vacuously rather than verifying directory creation. Tests 3 and 4 also leave state that bleeds into subsequent runs.

💡 Suggestion: add a trap cleanup and a per-test reset
cleanup() {
  rm -rf /tmp/gh-aw/agent /tmp/gh-aw/sandbox
}
trap cleanup EXIT

# Before Test 2, ensure a clean slate:
rm -rf /tmp/gh-aw/agent /tmp/gh-aw/sandbox
echo "Test 2: Creates expected directories from scratch"
...

This makes the test description match the actual precondition and ensures re-runs (e.g., bash create_gh_aw_tmp_dir_test.sh run twice in a row) stay deterministic.

@copilot please address this.

fi
fi

mkdir -p /tmp/gh-aw/agent

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/diagnosing-bugs] sandbox_dir is introduced for the guard but the subsequent mkdir calls use hardcoded strings. If the base path ever changes from /tmp/gh-aw, the guard and the mkdir calls drift silently.

💡 One-line fix: derive paths from a shared variable
gh_aw_dir="/tmp/gh-aw"
sandbox_dir="${gh_aw_dir}/sandbox"

# ... guard uses ${sandbox_dir} ...

mkdir -p "${gh_aw_dir}/agent"
mkdir -p "${sandbox_dir}/agent/logs"
echo "Created ${gh_aw_dir}/agent directory for agentic workflow temporary files"

This is a minor but zero-cost DRY improvement that removes a future maintenance footgun.

@copilot please address this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants