* feat(hooks): add FileChanged notifications for local sessions * fix(ci): preserve shell gate coverage and trim transcript data * fix(ci): flush debug prompts and isolate git fixtures * fix(hooks): respect ACP watcher ownership and skill registration * test: stabilize hosted recovery and failed spawn fixtures * fix(ci): budget the complete hosted integration fork * fix(runtime-broker): accept MariaDB CSI guard metadata * test: align schema baseline and hook dispatch waits * fix(ci): allow hosted shared fork to finish all classes * fix(hooks): preserve file watching across errors and cwd changes * fix(ci): isolate reference archive extraction per run * test(ci): keep reference archive checks dependency-free * fix(hooks): constrain recursive watches and repair CI * test(web-shell): synchronize verified connection return * fix(ci): correct runtime broker callback indentation * test(web-shell): match the managed workspace registration label Keep the remote workspace registration smoke checks in sync with the service label added by unified workspace navigation. Preserve exact text matching and all registration and replay assertions. * fix(ci): reduce standalone runtime fixture overhead * fix(hooks): ignore implicit atomic-write temporary files * test(managed-agent): recover initial node port conflicts * fix(hooks): observe parents before discovery anchors disappear * test(managed-agent): await stream cleanup before recovery teardown * test(managed-agent): align hosted shell fixture capabilities * test(managed-agent): let OS choose initial harness port * fix(ci): restore runner-local Java cache permissions * test(runtime-broker): use native execution profile paths * test(managed-agent): bind valid workspace upload probes * test(managed-agent): reuse the upload probe digest fixture * test(managed-agent): bind hook fault listeners to available ports * test(managed-agent): align the native profile path fixture * test(managed-agent): Wait for projection refusal retries * ci: Prepare daemon Java cache before Node preflight * test(managed-agent): check channel model calls after delivery * fix(hooks): observe explicitly registered symlink targets * test(managed-agent): separate consumption from relay snapshots * test(managed-agent): target tamper injection by resource key * test(managed-agent): exhaust attestation EOF retry in fault gates * fix(ci): bound RocketMQ release downloads * test(tui): await the restored assistant before capturing history * fix(ci): use a checksum-pinned RocketMQ mirror * fix(ci): provision missing browser test fonts * fix(ci): quote the font probe environment * fix(ci): prepare browser fonts without elevation * test(core): compare complete prune reports without ordering * fix(ci): repair only runner-local Java tool caches * test(managed-agent): qualify fixture JDBC templates * fix: isolate reconnect hooks and cancellation test fixtures Skip chat initialization so MCP reconnect cannot re-enable file hooks, and keep cancellation fixtures isolated from the background worktree scanner. * fix(ci): tolerate a missing Java cache helper on resumed releases Resuming an existing release checks out a tag that may predate the helper, which failed all five Java jobs before setup-java ran. * fix: finish the main merge for harness deadlines and workflow size Use the renamed settle-timeout accessor in the merged hosted-harness tests, and record sdk-java.yml's new size after the resumed-release cache guard. * style(managed-agent): restore import grouping in merged verification tests * test(managed-agent): require consumed child result mirrors * test(web-shell): center repository settings capture --------- Co-authored-by: yiliang114 <[email protected]> Co-authored-by: yungsen.hys <[email protected]> Co-authored-by: Shaojin Wen <[email protected]>
ECS Runner host cleanup
This directory contains host-level cleanup for dedicated Linux ECS GitHub Actions runner hosts. It complements per-job workflow cleanup; it does not replace it.
What it does
- Runs
qwen-docker-cleanuponce a day at 02:30 UTC, with up to 30 minutes of randomized delay. - Removes Qwen CI containers — sandbox-labelled or named
qwen-code-*— created more than 24 hours ago. - Prunes sandbox-labelled Qwen CI images and dangling images older than 24 hours.
- Reclaims unused Docker build cache last used more than 24 hours ago, with a 30 GB cache retention budget. Recent and in-use cache is preserved, so this is not a hard disk-usage cap. Failures mark the service as failed.
- Uses the existing Qwen sandbox daemon lock for labelled image pruning. Build-cache pruning relies on Docker's in-use protection and runs even when that lock is busy or missing.
- Changes the host
/tmpretention policy from 30 days to 1 day throughsystemd-tmpfiles(measured 2026-10-31: CI hosts generate ~120-260k temp files/day and a 7d steady state exceeds the tmpfs inode ceiling).
Use this only on dedicated CI hosts. A sandbox-labelled or qwen-code-*-named
container older than 24 hours is considered leaked and may be removed even if
it is still running.
Prerequisites
- Linux with systemd
- Docker available as
docker.service flockandtimeout- Root access for installation
- Runner workspaces under
/home/github-runner
Install or update
From a Qwen Code checkout, run:
sudo .github/scripts/ecs-runner/install-qwen-docker-cleanup.sh
The installer copies the cleanup command and systemd units into the host,
installs the one-day /tmp policy, reloads systemd, and enables the timer.
Run the same command again after pulling a newer version to update the host.
Merging changes does not update installed copies. Run the installer on every
host, including hosts that do not yet have the timer, and check each host's
timer and journal below. Updating the global Qwen CLI does not install this
cleanup service.
Verify
systemctl is-enabled qwen-docker-cleanup.timer
systemctl is-active qwen-docker-cleanup.timer
systemctl list-timers qwen-docker-cleanup.timer
systemd-tmpfiles --cat-config | grep '^D /tmp '
journalctl -u qwen-docker-cleanup.service -n 5 --no-pager
The expected /tmp policy is:
D /tmp 1777 root root 1d
The retention policy is enforced by the daily qwen-docker-cleanup.service
itself (ExecStartPost=systemd-tmpfiles --clean --prefix=/tmp), not by the
distro's systemd-tmpfiles-clean.timer — on hardened images that timer can
be masked, and it reports static (unverifiable via is-enabled) on stock
Ubuntu. The journal line above confirms the ExecStartPost ran.
Run manually
Drain the host first if it may contain a legitimate job or container running for more than 24 hours. Then run:
sudo systemctl start qwen-docker-cleanup.service
sudo journalctl -u qwen-docker-cleanup.service --since today
To apply the /tmp retention policy immediately:
sudo systemd-tmpfiles --clean --prefix=/tmp
Disable or uninstall
sudo systemctl disable --now qwen-docker-cleanup.timer
sudo systemctl clean --what=state qwen-docker-cleanup.timer
sudo rm -f /usr/local/sbin/qwen-docker-cleanup
sudo rm -f /etc/systemd/system/qwen-docker-cleanup.service
sudo rm -f /etc/systemd/system/qwen-docker-cleanup.timer
sudo rm -f /etc/tmpfiles.d/tmp.conf
sudo systemctl daemon-reload
Removing /etc/tmpfiles.d/tmp.conf restores the operating system's packaged
/tmp policy on the next systemd-tmpfiles invocation.
Scope
This cleanup intentionally does not delete runner workspaces, package caches, containerd leases, or containerd snapshots directly. Those resources require separate disk-pressure monitoring and a host drain before manual cleanup.
The PR review workflow creates a private scratch directory under RUNNER_TEMP,
exports it as TMPDIR, and asks the top-level agent to keep verification
copies there and pass the same requirement to its subagents. An always()
step removes that directory after artifact upload, including on failed or
cancelled reviews when cleanup steps can run. Cleanup failures are reported in
the job log.
This is a temporary-directory convention, not filesystem isolation: commands
that explicitly write elsewhere bypass it. Existing arbitrary copies in /tmp
remain covered only by the one-day policy. This service does not remove them
by name because they may belong to an active job. A later review job on the
same runner registration retries removing stale qwen-review-scratch.*
directories from RUNNER_TEMP; residue on an idle registration still requires
manual cleanup. Build-cache reclamation does not initialize unused data disks.
Font baseline
The web-shell smoke suite asserts exact text layout — for example that the
token-usage header wraps at the 320 px panel minimum — so its verdict depends
on the host's default sans-serif. The x64 ECS pool images ship
fonts-dejavu-core, and fontconfig resolves Latin text to DejaVu Sans. Fresh
ARM64 pool images (Ubuntu 26.04 minimal) do not: fontconfig falls back to
WenQuanYi Zen Hei for Latin as well as CJK, the same strings measure about
26 px narrower, and the wrap never happens (#15383).
Provision a host once with:
sudo .github/scripts/ecs-runner/install-qwen-font-baseline.sh
The installer is idempotent: on an already-provisioned host it installs nothing and only re-verifies. No runner restart is needed — each job's Chromium reads fontconfig when it starts.
Verify at any time:
# L0: the family every `sans-serif` request resolves to
bash .github/scripts/ecs-runner/check-font-baseline.sh
# L0 plus L1: text advance widths in the host's Playwright Chromium
bash .github/scripts/ecs-runner/check-font-baseline.sh --with-metrics
The web-shell smoke lane is pinned to ubuntu-latest to isolate its browsers
from shared-host network changes (run 40915920935 failed before app startup
with ERR_NETWORK_CHANGED on ARM64). Its retained Check host font baseline (self-hosted) step is dormant on that routing; should the lane return to a
shared host, that step runs the L0 half before installing dependencies, so a
drifted host fails in seconds with an actionable message instead of failing two
layout assertions 26 minutes into the suite. When L0 fails there, it downloads
the two DejaVu packages using private APT lists, extracts only their font data
into ${XDG_DATA_HOME:-$HOME/.local/share}/fonts/qwen-ci, refreshes the
runner's font cache, and verifies L0 again. This runs at the runner's existing
privilege level: CI does not elevate or execute the host-provisioning
installer. Healthy hosts require no download or extraction; acquisition
failures and remaining runner-user drift still fail the gate. Before moving
browser jobs back to a shared host, provide network isolation as well as this
font baseline; matching fonts alone does not prevent interrupted module loads.
L1 remains a manual/host-provisioning check requiring an installed Chromium.
Regression check
node --test .github/scripts/ecs-runner/*.test.mjs
The check runs the cleanup script with mocked host commands and temporary
lock files; it does not contact Docker or remove real containers. The font
suite stubs fc-match, apt-get, dpkg-query and Chromium the same way, so
it neither installs packages nor starts a browser.