Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 2 additions & 15 deletions containers/agent/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -892,24 +892,11 @@ AWFEOF
# Set comprehensive PATH for host binaries
# Include standard paths plus tool cache locations (GitHub Actions)
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
# Prepend entries from $GITHUB_PATH file (written by setup-* actions) so they
# take priority over the hostedtoolcache scan below. This replicates what the
# Actions runner normally does with GITHUB_PATH, preserving the version chosen
# by setup-ruby / setup-python / setup-node / etc.
if [ -n "${GITHUB_PATH}" ] && [ -f "${GITHUB_PATH}" ]; then
_github_path_prefix=""
while IFS= read -r _gp_entry; do
_gp_entry="${_gp_entry%$'\r'}" # strip trailing CR (Windows-style CRLF files)
[ -z "${_gp_entry}" ] && continue
_github_path_prefix="${_github_path_prefix}${_gp_entry}:"
done < "${GITHUB_PATH}"
[ -n "${_github_path_prefix}" ] && export PATH="${_github_path_prefix}${PATH}"
fi
# Dynamically scan toolcache roots for all installed tool bin directories.
# This covers tools installed by setup-* actions on both hosted runners
# (/opt/hostedtoolcache) and self-hosted runners ($HOME/work/_tool).
# Append (not prepend) so that $GITHUB_PATH entries and standard paths above
# retain priority; discovered toolcache dirs serve as fallbacks only.
# Append (not prepend) so standard paths above retain priority; discovered
# toolcache dirs serve as fallbacks only.
append_toolcache_bins() {
local toolcache_root="$1"
if [ ! -d "${toolcache_root}" ]; then
Expand Down
24 changes: 10 additions & 14 deletions docs/environment.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,29 +161,25 @@ The following environment variables are set internally by the firewall and used

## GitHub Actions `setup-*` Tool Availability

Tools installed by GitHub Actions `setup-*` actions (e.g., `astral-sh/setup-uv`, `actions/setup-node`, `ruby/setup-ruby`, `actions/setup-python`) are **automatically available inside the AWF chroot**. This works by:
Tools installed by GitHub Actions `setup-*` actions (e.g., `astral-sh/setup-uv`, `actions/setup-node`, `ruby/setup-ruby`, `actions/setup-python`) are **automatically available inside the AWF chroot** when their installation paths can be recovered from the runner environment. AWF builds `AWF_HOST_PATH` from:

1. `setup-*` actions write their tool bin directories to the `$GITHUB_PATH` file.
2. AWF reads this file at startup and merges its entries (prepended, higher priority) into `AWF_HOST_PATH`.
3. The chroot entrypoint exports `AWF_HOST_PATH` as `PATH` inside the chroot, so tools like `uv`, `node`, `python3`, `ruby`, etc. resolve correctly.
1. The `PATH` visible to the `awf` process at startup.
2. Bin directories discovered under `RUNNER_TOOL_CACHE` (for tools installed by setup actions into the Actions tool cache, such as `actions/setup-node`).

The chroot entrypoint exports `AWF_HOST_PATH` as `PATH` inside the chroot, so tools like `uv`, `node`, `python3`, `ruby`, etc. resolve correctly.

This behavior was introduced in **awf v0.60.0** and is active automatically — no extra flags are required.

**Fallback behavior:** If `GITHUB_PATH` is not set (e.g., outside GitHub Actions or on self-hosted runners that don't set it), AWF uses `process.env.PATH` as the chroot PATH. If `sudo` has reset `PATH` before AWF runs and `GITHUB_PATH` is also absent, the tool's directory may be missing from the chroot PATH. In that case, invoke the tool via its absolute path or ensure `GITHUB_PATH` is set.
**Important:** `$GITHUB_PATH` is a per-step command file. The Actions runner consumes each step's file after that step completes and folds those entries into `PATH` for later steps. AWF does not read `$GITHUB_PATH` for path recovery; for previous setup steps, AWF relies on the inherited `PATH` and `RUNNER_TOOL_CACHE` recovery.

**Troubleshooting:** Run AWF with `--log-level debug` to see whether `GITHUB_PATH` is set and how many entries were merged:
**Fallback behavior:** If `RUNNER_TOOL_CACHE` is not set (e.g., outside GitHub Actions), AWF uses `process.env.PATH` as the chroot PATH. If `sudo` has reset `PATH` before AWF runs and no tool-cache location is available, the tool's directory may be missing from the chroot PATH. In that case, invoke the tool via its absolute path or preserve the required path when invoking AWF.

```
[DEBUG] Merged 3 path(s) from $GITHUB_PATH into AWF_HOST_PATH
```

If you see instead:
**Troubleshooting:** Run AWF with `--log-level debug` to see how many runner tool cache entries were merged:

```
[DEBUG] GITHUB_PATH env var is not set; skipping $GITHUB_PATH file merge …
[DEBUG] Merged 1 runner tool cache bin path(s) into AWF_HOST_PATH
```

the runner did not set `GITHUB_PATH`, and the tool's bin directory must already be in `$PATH` at AWF launch time.
If the missing tool was installed by a previous setup step, verify that `RUNNER_TOOL_CACHE` is set and points at the Actions tool cache.

## Debugging Environment Variables

Expand Down
154 changes: 0 additions & 154 deletions src/docker-manager-utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ import {
} from './host-identity';
import {
extractGhHostFromServerUrl,
readGitHubPathEntries,
mergeGitHubPathEntries,
readGitHubEnvEntries,
readEnvFile,
} from './github-env';
Expand Down Expand Up @@ -376,111 +374,6 @@ describe('docker-manager utilities', () => {
});
});

describe('readGitHubPathEntries', () => {
let tmpDir: string;

beforeEach(() => {
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'awf-github-path-'));
});

afterEach(() => {
fs.rmSync(tmpDir, { recursive: true, force: true });
});

it('should return empty array when GITHUB_PATH is not set', () => {
const originalGithubPath = process.env.GITHUB_PATH;
delete process.env.GITHUB_PATH;

try {
const result = readGitHubPathEntries();
expect(result).toEqual([]);
} finally {
if (originalGithubPath !== undefined) {
process.env.GITHUB_PATH = originalGithubPath;
}
}
});

it('should return empty array when GITHUB_PATH file does not exist', () => {
const originalGithubPath = process.env.GITHUB_PATH;
process.env.GITHUB_PATH = '/nonexistent/path/to/github_path_file';

try {
const result = readGitHubPathEntries();
expect(result).toEqual([]);
} finally {
if (originalGithubPath !== undefined) {
process.env.GITHUB_PATH = originalGithubPath;
} else {
delete process.env.GITHUB_PATH;
}
}
});

it('should read path entries from GITHUB_PATH file', () => {
const pathFile = path.join(tmpDir, 'add_path');
fs.writeFileSync(pathFile, '/opt/hostedtoolcache/Ruby/3.3.10/x64/bin\n/opt/hostedtoolcache/Python/3.12.0/x64/bin\n');

const originalGithubPath = process.env.GITHUB_PATH;
process.env.GITHUB_PATH = pathFile;

try {
const result = readGitHubPathEntries();
expect(result).toEqual([
'/opt/hostedtoolcache/Ruby/3.3.10/x64/bin',
'/opt/hostedtoolcache/Python/3.12.0/x64/bin',
]);
} finally {
if (originalGithubPath !== undefined) {
process.env.GITHUB_PATH = originalGithubPath;
} else {
delete process.env.GITHUB_PATH;
}
}
});

it('should handle empty lines and whitespace in GITHUB_PATH file', () => {
const pathFile = path.join(tmpDir, 'add_path');
fs.writeFileSync(pathFile, ' /opt/hostedtoolcache/Ruby/3.3.10/x64/bin \n\n \n/opt/dart-sdk/bin\n');

const originalGithubPath = process.env.GITHUB_PATH;
process.env.GITHUB_PATH = pathFile;

try {
const result = readGitHubPathEntries();
expect(result).toEqual([
'/opt/hostedtoolcache/Ruby/3.3.10/x64/bin',
'/opt/dart-sdk/bin',
]);
} finally {
if (originalGithubPath !== undefined) {
process.env.GITHUB_PATH = originalGithubPath;
} else {
delete process.env.GITHUB_PATH;
}
}
});

it('should handle empty GITHUB_PATH file', () => {
const pathFile = path.join(tmpDir, 'add_path');
fs.writeFileSync(pathFile, '');

const originalGithubPath = process.env.GITHUB_PATH;
process.env.GITHUB_PATH = pathFile;

try {
const result = readGitHubPathEntries();
expect(result).toEqual([]);
} finally {
if (originalGithubPath !== undefined) {
process.env.GITHUB_PATH = originalGithubPath;
} else {
delete process.env.GITHUB_PATH;
}
}
});
});

describe('parseGitHubEnvFile (via readGitHubEnvEntries)', () => {
let tmpDir: string;
let originalGithubEnv: string | undefined;
Expand Down Expand Up @@ -613,53 +506,6 @@ describe('docker-manager utilities', () => {
});
});

describe('mergeGitHubPathEntries', () => {
it('should return current PATH when no github path entries', () => {
const result = mergeGitHubPathEntries('/usr/bin:/usr/local/bin', []);
expect(result).toBe('/usr/bin:/usr/local/bin');
});

it('should prepend github path entries to current PATH', () => {
const result = mergeGitHubPathEntries(
'/usr/bin:/usr/local/bin',
['/opt/hostedtoolcache/Ruby/3.3.10/x64/bin']
);
expect(result).toBe('/opt/hostedtoolcache/Ruby/3.3.10/x64/bin:/usr/bin:/usr/local/bin');
});

it('should not duplicate entries already in PATH', () => {
const result = mergeGitHubPathEntries(
'/opt/hostedtoolcache/Ruby/3.3.10/x64/bin:/usr/bin:/usr/local/bin',
['/opt/hostedtoolcache/Ruby/3.3.10/x64/bin']
);
expect(result).toBe('/opt/hostedtoolcache/Ruby/3.3.10/x64/bin:/usr/bin:/usr/local/bin');
});

it('should handle multiple new entries', () => {
const result = mergeGitHubPathEntries(
'/usr/bin',
['/opt/hostedtoolcache/Ruby/3.3.10/x64/bin', '/opt/dart-sdk/bin']
);
expect(result).toBe('/opt/hostedtoolcache/Ruby/3.3.10/x64/bin:/opt/dart-sdk/bin:/usr/bin');
});

it('should handle mix of new and existing entries', () => {
const result = mergeGitHubPathEntries(
'/opt/hostedtoolcache/Ruby/3.3.10/x64/bin:/usr/bin',
['/opt/hostedtoolcache/Ruby/3.3.10/x64/bin', '/opt/dart-sdk/bin']
);
expect(result).toBe('/opt/dart-sdk/bin:/opt/hostedtoolcache/Ruby/3.3.10/x64/bin:/usr/bin');
});

it('should handle empty current PATH', () => {
const result = mergeGitHubPathEntries(
'',
['/opt/hostedtoolcache/Ruby/3.3.10/x64/bin']
);
expect(result).toBe('/opt/hostedtoolcache/Ruby/3.3.10/x64/bin');
});
});

describe('readEnvFile', () => {
let tmpDir: string;

Expand Down
Loading
Loading