Skip to content
Merged
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
16 changes: 16 additions & 0 deletions .github/agents/self-hosted-runner-doctor.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,9 @@ Prefer the narrowest match. Examples:
- `error mounting "/dev/null" to .../home/.npmrc: create mountpoint ...: read-only file system` (or `.docker/config.json`, `.composer/auth.json`) on `arc-dind` with `--docker-host-path-prefix` set → A23 (a surviving prefixed `${workDir}-chroot-home:/host$HOME` mount is paired with `/dev/null:/host$HOME/<credential>:ro` overlays because prefixed sources were compared with unprefixed `workDir`/`effectiveHome`; fixed in github/gh-aw-firewall#7998)
- `a network with name awf-net exists but was not created for project` → B27 (orphaned fixed-name `awf-net` from a prior run on a persistent self-hosted runner; fixed in github/gh-aw-firewall#7817)
- TLS/certificate verification failure from api-proxy against a custom `--openai-api-target`/`--anthropic-api-target` internal endpoint using a private/corporate CA → B28 (api-proxy sidecar had no custom CA trust extension point; fixed in github/gh-aw-firewall#7816 with `apiProxy.caCert`/`--api-proxy-ca-cert`)
- `context-rebuild circuit breaker tripped` together with a failed `cd` into the expected workspace path → B29 (container-workdir not bind-mounted into the chroot; fixed in github/gh-aw-firewall#8021)
- `awf logs summary` reports "no log sources found" after a pre-egress startup failure with no Squid `access.log` → B30 (check preserved logs dir for `awf-startup-error.json`; fixed in github/gh-aw-firewall#8023)
- Copilot calls on Business/Enterprise/GHEC use the wrong Authorization scheme specifically for a fine-grained PAT (`github_pat_...`) → C10 (fixed in github/gh-aw-firewall#8038)

### 4. Check for known gaps and notable fixes

Expand Down Expand Up @@ -179,6 +182,10 @@ B26 / github/gh-aw#54371, github/gh-aw-firewall#7615, github/gh-aw-firewall#7635

B27 / github/gh-aw#56463, github/gh-aw-firewall#7809, github/gh-aw-firewall#7817 — Docker Compose refuses to start AWF containers with repeated warnings `a network with name awf-net exists but was not created for project "awf-<id>"` on persistent self-hosted runners. `generateDockerCompose()` named the Docker network `awf-net` without `external: true` or a project-scoped/unique name, so a stale network left behind by a killed or timed-out previous AWF run could be treated as belonging to a different project and block startup. **Fixed in AWF (PR github/gh-aw-firewall#7817, merged 2026-08-28):** AWF reclaims orphaned fixed-name Docker networks (`awf-net`) before `docker compose up`. Upgrade AWF to include github/gh-aw-firewall#7817. Probe: `docker network ls | grep awf-net` before a run; a stale `awf-net` not associated with a live compose project reproduces the mode, and patched AWF reclaims it automatically before compose up.

B29 / github/gh-aw-firewall#8015, github/gh-aw-firewall#8021 — `codex`-engine workflows can abort with `report_incomplete` and "context-rebuild circuit breaker tripped" after repeated failures to `cd` into the expected workspace path because `--container-workdir` was not guaranteed to be bind-mounted inside the chroot. **Fixed in AWF (PR github/gh-aw-firewall#8021, merged 2026-09-02):** `buildContainerWorkDirMounts()` emits an explicit `<workdir>:<workdir>:rw` mount when needed, refuses hidden roots, and warns when the host directory is absent. Upgrade AWF to include github/gh-aw-firewall#8021.

B30 / github/gh-aw-firewall#8014, github/gh-aw-firewall#8023 — A pre-egress AWF startup failure can leave no Squid `access.log`, causing `awf logs summary`/`stats` to report only "no log sources found". **Fixed in AWF (PR github/gh-aw-firewall#8023, merged 2026-09-02):** AWF writes redacted `awf-startup-error.json`; log discovery and stats/summary recognize it. Check the preserved proxy-logs directory for that file and upgrade AWF to include github/gh-aw-firewall#8023.

D8 / github/gh-aw-firewall#6401, github/gh-aw-firewall#6326 — Under `--container-runtime gvisor` or raw `runsc`, MCP calls to the gateway at `172.30.0.1:8080` could be misrouted through Squid and fail with `403 ERR_ACCESS_DENIED` because gVisor's userspace netstack does not use the host iptables DNAT bypass. **Fixed in AWF (PR github/gh-aw-firewall#6401)**: `runtimeUsesIptables()` now skips `awf-iptables-init` for `gvisor`, its `runsc` alias, and `sbx`, and the MCP gateway plus `host.docker.internal` are added to `NO_PROXY` for proxy-aware clients. Caveat: proxy-unaware raw sockets (for example `/dev/tcp`) still fail with `No route to host` under gVisor.

D9 / github/gh-aw-firewall#6336 — sbx microVMs previously mounted the entire host `$HOME`, exposing credentials such as `~/.aws/credentials`, `~/.ssh/id_rsa`, and `~/.docker/config.json`. **Fixed in AWF (PR github/gh-aw-firewall#6336)**: sbx now mounts only whitelisted home subdirectories, and `scrubHomeCredentials()` / `restoreHomeCredentials()` temporarily move nested credential files out of the mounted tree during sandbox lifetime.
Expand All @@ -195,6 +202,8 @@ C8 / github/gh-aw-firewall#5872, github/gh-aw-firewall#6237 — Copilot Business

C9 / github/gh-aw-firewall#6989, github/gh-aw-firewall#6991 — `400 bad request: Authorization header is badly formatted` on the derived GHEC data-residency Copilot target `copilot-api.<tenant>.ghe.com` is distinct from C8's `api.business.githubcopilot.com` case. `copilotTargetRequiresGitHubTokenPrefix()` did not recognize inferred `copilot-api.*.ghe.com` endpoints as GitHub-hosted GHEC data-residency targets requiring the `token` prefix, so GitHub OAuth/PAT credentials were sent as `Bearer`. **Fixed in AWF (PR github/gh-aw-firewall#6991, merged 2026-08-08):** `isGhecCopilotApiTarget()` sends GitHub OAuth/PAT credentials with `token` for derived `copilot-api.*.ghe.com` targets while preserving `Bearer` for BYOK credentials and the standard Copilot endpoint. Upgrade AWF to include github/gh-aw-firewall#6991.

C10 / github/gh-aw-firewall#8035, github/gh-aw-firewall#8038 — Fine-grained GitHub PATs (`github_pat_...`) sent to Copilot Business, Enterprise, and canonical GHEC targets used the wrong Authorization scheme; canonical GHEC `/models` discovery also missed GitHub-hosted handling, and a legacy isolation placeholder could override the real credential. **Fixed in AWF (PR github/gh-aw-firewall#8038, merged 2026-09-02):** credential kind now selects `Bearer` for fine-grained PATs, GHEC catalog calls receive `X-GitHub-Api-Version: 2026-07-01`, integration identity uses the documented precedence, and the legacy placeholder is rejected for inference. Upgrade AWF to include github/gh-aw-firewall#8038.

D7 / github/gh-aw-firewall#6260, github/gh-aw-firewall#6261, github/gh-aw-firewall#6276 — Claude Code (Bun/JSC) crashes with `SIGSEGV`/`SIGABRT` under `--container-runtime gvisor` because JSC JIT is incompatible with gVisor's W^X memory restrictions. **AWF (PR github/gh-aw-firewall#6276) automatically sets `BUN_JSC_useJIT=0`** at runtime via `buildToolEnvironment()` when Claude runs under gVisor — no workflow change required. For older AWF builds without github/gh-aw-firewall#6276, pass `--env BUN_JSC_useJIT=0` as a manual fallback.

## Output Requirements
Expand Down Expand Up @@ -299,6 +308,9 @@ Establish these facts before matching a failure mode:
| B27 | Docker Compose refuses to start AWF containers with repeated warnings: `a network with name awf-net exists but was not created for project "awf-<id>"`, blocking container startup on a persistent (non-ephemeral) self-hosted runner | `generateDockerCompose()` in `src/compose-generator.ts` names the Docker network `awf-net` without `external: true` or a project-scoped/unique name. If a prior AWF run's network was not cleaned up (killed/timed-out process, orphaned network from a previous invocation), Compose treats the pre-existing `awf-net` as belonging to a different (or no) project and refuses to attach, since network names are not scoped per-project by default | **Fixed in AWF (PR github/gh-aw-firewall#7817, merged 2026-08-28):** AWF reclaims orphaned fixed-name Docker networks (`awf-net`) before `docker compose up`. Upgrade AWF to include github/gh-aw-firewall#7817. | `docker network ls \| grep awf-net` before a run — presence of a stale `awf-net` not associated with a live compose project reproduces the mode; on patched AWF the stale network is reclaimed automatically before compose up | github/gh-aw#56463, github/gh-aw-firewall#7809, github/gh-aw-firewall#7817 |
| B28 | Custom `apiProxy` targets pointing at an internal/corporate LLM router (`--openai-api-target`, `--anthropic-api-target`, etc.) fail TLS verification when the upstream endpoint's certificate chains to a private or corporate CA not present in the api-proxy sidecar's trust store | The `containers/api-proxy` Node.js sidecar had no supported way to extend its trust store for custom upstream targets; the only workarounds were disabling certificate verification (insecure) or baking a custom CA into a rebuilt image | **Fixed in AWF (PR github/gh-aw-firewall#7816, merged 2026-08-28):** new `apiProxy.caCert` config field and `--api-proxy-ca-cert <path>` CLI flag bind-mount the host CA file read-only into the api-proxy container at `/usr/local/share/ca-certificates/awf-upstream-ca.crt` and set `NODE_EXTRA_CA_CERTS` to that path, so Node trusts the additional CA alongside its built-in roots without disabling verification. Upgrade AWF to include github/gh-aw-firewall#7816. | Inspect generated `docker-compose.yml` for the api-proxy service — a read-only bind mount to `/usr/local/share/ca-certificates/awf-upstream-ca.crt` and `NODE_EXTRA_CA_CERTS` env var confirm the fix is active; reproduce the failure pre-fix with `awf --openai-api-target <internal-host> --allow-domains <internal-host> -- <cmd>` against an endpoint using a private-CA certificate | github/gh-aw-firewall#7807, github/gh-aw-firewall#7816 |

| B29 | `codex`-engine (and similar) workflows abort with `report_incomplete`: "context-rebuild circuit breaker tripped" after repeatedly failing to `cd` into the expected workspace path (for example `/home/runner/work/<repo>/<repo>: No such file or directory`) | `--container-workdir` sets the agent's starting directory to a host-style absolute path, but that path was not guaranteed to be bind-mounted inside the chroot. If it was outside the workspace mount, `/tmp`, system mounts, `$HOME` tool mounts, or an explicit `--mount`, `entrypoint.sh` silently fell back to `/`, causing repeated context-rebuild retries | **Fixed in AWF (PR github/gh-aw-firewall#8021, merged 2026-09-02):** `buildContainerWorkDirMounts()` in `src/services/agent-volumes/workspace-mounts.ts` emits an explicit `<workdir>:<workdir>:rw` bind mount when the configured workdir is not already reachable inside the chroot. It refuses paths inside deliberately hidden roots and warns when the host directory does not exist. Upgrade AWF to include github/gh-aw-firewall#8021. | Inspect generated `docker-compose.yml` for a bind mount matching `--container-workdir` when no other mount covers it; check startup logs for a workdir-not-found warning instead of a silent `/` fallback; reproduce with a workdir outside every default mount | github/gh-aw-firewall#8015, github/gh-aw-firewall#8021 |
| B30 | AWF-sandbox workflows fail before Squid starts (for example from a bad bind-mount spec), leaving no Squid `access.log`; `awf logs summary`/`awf logs stats` report only "no log sources found" | AWF had no mechanism to preserve startup-phase failure detail when containers never produced Squid logs, so the underlying cause was lost | **Fixed in AWF (PR github/gh-aw-firewall#8023, merged 2026-09-02):** AWF writes a redacted `awf-startup-error.json` (timestamp, phase, failure message) into the proxy logs directory on startup abort; log discovery recognizes it via `AWF_LOGS_DIR` and preserved `/tmp/squid-logs-*` discovery, and stats/summary include the diagnostic. Upgrade AWF to include github/gh-aw-firewall#8023. | After a pre-egress failure, check the preserved proxy-logs directory for `awf-startup-error.json`; run `awf logs summary` and confirm it surfaces the startup diagnostic | github/gh-aw-firewall#8014, github/gh-aw-firewall#8023 |

## Category C — GHES / GHEC / `ghe.com`

| ID | Signal | Root cause | Fix / flag | Probe | Citations |
Expand All @@ -312,6 +324,7 @@ Establish these facts before matching a failure mode:
| C7 | `awf-cli-proxy` DIFC-proxy liveness probe loops retrying; cli-proxy logs show `diagnosis=unknown` (AWF < v0.27.12) or `diagnosis=reachable-but-api-error (HTTP NNN)` with a `*.ghe.com` hint (AWF ≥ v0.27.12); AWF fails to start | DIFC proxy is reachable but the forwarded `gh api rate_limit` call returns an HTTP error because the DIFC proxy is not enterprise-host-aware on data-residency `*.ghe.com` tenants | **Partially mitigated**: upgrade to AWF ≥ v0.27.12 for a targeted `*.ghe.com` hint and HTTP status in cli-proxy logs; root cause (DIFC proxy enterprise-host awareness) is **unresolved** in companion projects (github/gh-aw-mcpg#8202, github/gh-aw#41911) | Check `GITHUB_SERVER_URL` for `*.ghe.com`; inspect cli-proxy logs for `diagnosis=unknown` or `reachable-but-api-error (HTTP NNN)`; confirm AWF ≥ v0.27.12 for the targeted hint | #5615, #5616 |
| C8 | `400 bad request: Authorization header is badly formatted` on **GHEC (`*.ghe.com`)** runners when `COPILOT_API_TARGET=api.business.githubcopilot.com`; Copilot Business calls receive `Bearer` instead of required `token` prefix. Reproduced on AWF v0.27.13 and v0.27.16; or `400` persists even after upgrading past github/gh-aw-firewall#5872 when `COPILOT_PROVIDER_API_KEY=dummy-byok-key-for-offline-mode` is set by `gh-aw` offline mode | Two distinct root causes: **(1) Pre-#5872:** `copilotTargetRequiresGitHubTokenPrefix()` checked `AWF_PLATFORM_TYPE` guard first. On GHEC, AWF auto-injects `AWF_PLATFORM_TYPE=ghec`, which short-circuited to `false` before querying the `GITHUB_TOKEN_PREFIX_COPILOT_TARGETS` catalog. **(2) Post-#5872 / #6237:** `gh-aw` offline mode sets `COPILOT_PROVIDER_API_KEY=dummy-byok-key-for-offline-mode` as a sentinel; AWF before #6237 treated it as a real BYOK key, which took precedence over and suppressed the GitHub-token auth path | **Fixed in AWF (PR github/gh-aw-firewall#5872)**: catalog endpoints (`api.enterprise.githubcopilot.com`, `api.business.githubcopilot.com`) are now checked first (always `token`); the platform-type guard now only affects the GHES heuristic for unknown targets. Upgrade to AWF version including github/gh-aw-firewall#5872. **Additional fix (PR github/gh-aw-firewall#6237):** treats `dummy-byok-key-for-offline-mode` as a non-credential sentinel (same class as AWF placeholder tokens), restoring the GitHub-token auth path on Business/Enterprise targets. | `awf --version`; inspect api-proxy logs for 400 on `api.business.githubcopilot.com`; confirm `AWF_PLATFORM_TYPE=ghec` is set; check whether `COPILOT_PROVIDER_API_KEY=dummy-byok-key-for-offline-mode` is present | github/gh-aw-firewall#5871, github/gh-aw-firewall#5872, github/gh-aw-firewall#6237 |
| C9 | `400 bad request: Authorization header is badly formatted` specifically on the **derived GHEC data-residency Copilot target** `copilot-api.<tenant>.ghe.com` (distinct from C8's `api.business.githubcopilot.com`); receives `Bearer` instead of required `token` prefix | `copilotTargetRequiresGitHubTokenPrefix()` did not recognize inferred `copilot-api.*.ghe.com` endpoints as GitHub-hosted GHEC data-residency targets requiring the `token` prefix | **Fixed in AWF (PR github/gh-aw-firewall#6991, merged 2026-08-08)**: added `isGhecCopilotApiTarget()` in `containers/api-proxy/providers/copilot-auth.js`; GitHub OAuth/PAT credentials now sent with `token` prefix to derived `copilot-api.*.ghe.com` targets while `Bearer` is preserved for BYOK credentials and the standard Copilot endpoint. Upgrade AWF to include github/gh-aw-firewall#6991. | Inspect api-proxy logs for 400 on `copilot-api.<tenant>.ghe.com`; confirm `GITHUB_SERVER_URL` is `*.ghe.com` and the target is the derived Copilot endpoint (not `api.business.githubcopilot.com`) | github/gh-aw-firewall#6989, github/gh-aw-firewall#6991 |
| C10 | Fine-grained GitHub PATs (`github_pat_...`) sent to Copilot Business, Enterprise, and canonical GHEC (`copilot-api.<tenant>.ghe.com`) targets used `token` instead of required `Bearer`; canonical GHEC targets also missed GitHub-hosted `/models` discovery handling, and a legacy isolation placeholder could override the real GitHub credential | The api-proxy Copilot adapter selected the Authorization scheme by host without classifying credential kind, did not treat `copilot-api.*.ghe.com` as a GitHub-hosted catalog endpoint, and did not reject `placeholder-token-for-credential-isolation` as a non-credential sentinel | **Fixed in AWF (PR github/gh-aw-firewall#8038, merged 2026-09-02):** resolved credentials are classified before scheme selection (`Bearer` for `github_pat_` tokens, preserving `token` for OAuth/classic PATs and BYOK behavior); canonical GHEC `/models` calls receive `X-GitHub-Api-Version: 2026-07-01`; integration identity resolves from `COPILOT_INTEGRATION_ID`, `GITHUB_COPILOT_INTEGRATION_ID`, then the default; the legacy placeholder is rejected for inference. Upgrade AWF to include github/gh-aw-firewall#8038. | Inspect api-proxy reflection diagnostics for `credential_kind`, `selected_scheme`, `inference_credential_source`, and `integration_id_source`; confirm fine-grained PATs receive `Bearer` and GHEC `/models` calls include the API-version header | github/gh-aw-firewall#8035, github/gh-aw-firewall#8038 |
Comment thread
Copilot marked this conversation as resolved.

## Category D — Alternative runtimes and adjacent gaps

Expand Down Expand Up @@ -390,6 +403,9 @@ Establish these facts before matching a failure mode:
| `error mounting "/dev/null" to .../home/.npmrc: create mountpoint ...: read-only file system` (or `.docker/config.json`, `.composer/auth.json`) on `arc-dind` with `--docker-host-path-prefix` set | A23 (`filterAgentVolumesForSysroot()` compared prefixed mount sources against unprefixed `workDir`/`effectiveHome`, so the bogus chroot-home mount wasn't dropped; fixed in github/gh-aw-firewall#7998) |
| `a network with name awf-net exists but was not created for project` | B27 (orphaned fixed-name `awf-net` from a prior run on a persistent self-hosted runner; fixed in github/gh-aw-firewall#7817) |
| TLS/certificate verification failure from api-proxy against a custom `--openai-api-target`/`--anthropic-api-target` internal endpoint using a private/corporate CA | B28 (api-proxy sidecar had no custom CA trust extension point; fixed in github/gh-aw-firewall#7816 with `apiProxy.caCert`/`--api-proxy-ca-cert`) |
| `context-rebuild circuit breaker tripped` together with a failed `cd` into the expected workspace path | B29 (container-workdir not bind-mounted into the chroot; fixed in github/gh-aw-firewall#8021) |
| `awf logs summary` reports "no log sources found" after a pre-egress startup failure with no Squid `access.log` | B30 (check preserved logs dir for `awf-startup-error.json`; fixed in github/gh-aw-firewall#8023) |
| Copilot calls on Business/Enterprise/GHEC use the wrong Authorization scheme specifically for a fine-grained PAT (`github_pat_...`) | C10 (fixed in github/gh-aw-firewall#8038) |
| `spawn /usr/local/bin/copilot ENOENT` specifically on a tool-cache **hit** (`GITHUB_PATH` already set by the installer) | B23 (gh-aw's `activate_cached_copilot_bin()` skips the `/usr/local/bin/copilot` wrapper on cache hits while the compiler harness spawns that hardcoded path; AWF-side fixed via `ensure_usr_local_bin_shims()`/`prepare_usr_local_bin_overlay()` in github/gh-aw-firewall#7245; durable upstream fix still tracked in github/gh-aw-firewall#7130, open) |
| `runc` mountpoint creation failure for `/dev/null` credential overlays under `/host$HOME` on `runner.topology: arc-dind` | A20 |
| `mkdir -p .../.m2` failing under `set -e` in agent entrypoint on `arc-dind` | A20 |
Expand Down
Loading
Loading