Skip to content

ci: run the Go agent E2E suite on Windows, and fail if it skips - #60

Merged
Sniperlyf3 merged 2 commits into
mainfrom
claude/meowsshapi-prs-issues-k2bx2m
Sep 25, 2026
Merged

Sniperlyf3 merged 2 commits into
mainfrom
claude/meowsshapi-prs-issues-k2bx2m

Conversation

@Sniperlyf3

Copy link
Copy Markdown
Owner

Problem

windows-e2e has never run any of the 22 agent E2E tests, and still went green.

  • Its only go test step ran before the job downloaded the binaries.
  • findE2EBinary's fallback name was hardcoded ..._linux_amd64, so on Windows (which also appends .exe) the file could never be found. Every test skipped.
  • Nothing would catch the Linux step doing the same thing, because go test counts skips as a pass.

Fix

  • New windows-e2e step: it runs go test -v ./cmd/meowshell/ after the artifact download, with MEOWSHELL/TAILCAT set explicitly to the Windows binaries.
    • It runs before the testderp step, because these tests use TS_DEBUG_TAILCAT_LOCAL_DERP per server.
    • The job timeout goes from 15 to 25 minutes.
  • findE2EBinary: it now builds the fallback name from GOOS/GOARCH the way build.sh names its output, and prints fixed markers when a lookup resolves or skips. TestE2EDistNameMatchesBuildSh pins the naming.
  • e2e/require-agent-e2e-ran.sh: wraps both E2E steps (Linux and Windows). It fails the step if any test skipped for a missing binary, or if no lookup resolved at all.
  • .gitattributes: *.sh is now eol=lf. The Windows runner checks files out with CRLF line endings (autocrlf), which breaks bash scripts.
  • CLAUDE.md: updated to describe the new Windows state.

Verification (linux/amd64)

  • Vet: go vet ./... and GOOS=windows go vet ./cmd/meowshell/ pass.
  • Guard with real binaries: "26 binary lookups resolved, none skipped".
  • Excluded test: TestListenUnixAllowsRootOwnedStickyTmpStyleParent fails only as root, as it does on main. It was excluded from the local run; CI runners aren't root.
  • Mutation checks:
    • dist/ moved aside: 22 tests skip and go test passes, but the guard fails the step.
    • -run filtered to the naming test: the guard fails because nothing resolved.
    • Nonexistent explicit binaries: go test fails and the guard passes the failure through.
    • .exe suffix dropped: TestE2EDistNameMatchesBuildSh fails.
  • Not yet proven: none of this has run on Windows. This PR's windows-e2e run is the first time the suite runs there, so real platform failures are possible.

🤖 Generated with Claude Code

https://claude.ai/code/session_013WM5HyhwRLKBDyHR6qitkk


Generated by Claude Code

windows-e2e never ran any of the 22 agent E2E tests. Its only go test
step ran before the job downloaded the binaries, and findE2EBinary's
../../dist fallback was the literal "..._linux_amd64" at every call
site, so on Windows (where build.sh also appends .exe) it looked for a
file that could never be there. Every test skipped and the job went
green. Nothing in either job would notice the Linux step going the
same way either: go test reports skips as a pass.

windows-e2e now runs `go test -v ./cmd/meowshell/` in a new step after
the artifact is downloaded into its private staging directory, with
MEOWSHELL/TAILCAT set explicitly to the Windows binaries (returned
unchecked, so a broken binary fails instead of skipping). It runs
before the testderp step, which exports TAILCAT_DERPMAP_URL; these
tests use TS_DEBUG_TAILCAT_LOCAL_DERP per server, as in the build job.
The job timeout goes from 15 to 25 minutes to cover the second run.

findE2EBinary now takes the bare binary name and builds the fallback
from runtime.GOOS/GOARCH the way build.sh's build() does (armv7 for
arm, .exe on Windows). It logs a fixed marker when it resolves a binary
and puts one in its skip message. e2e/require-agent-e2e-ran.sh wraps
the go test in both E2E steps and fails the step if any test skipped
for a missing binary, or if no lookup resolved at all (tests filtered
out, -v dropped). *.sh is pinned to eol=lf so the windows-latest
autocrlf=true checkout does not hand Git Bash a CRLF script.

The .NET E2E suite needs no change for this: it runs only in the
Linux dotnet job, and its lookup has no hardcoded platform name.
CLAUDE.md now says so, and describes the new Windows state.

Verified on linux/amd64 with .tailcat-src set up per CLAUDE.md:
go vet ./... and GOOS=windows go vet ./... pass, as does
GOOS=windows go test -c ./cmd/meowshell/. go test ./... passes except
TestListenUnixAllowsRootOwnedStickyTmpStyleParent, which fails the
same way on the parent commit (it only runs as root, and this sandbox
runs as root). Through the guard with explicit binaries: 26 lookups
resolved, 0 skips, exit 0; again with the env vars unset, going
through the new fallback name. Mutations: dist/ moved aside -> 22 skips,
go test PASS, guard exit 1; -run limited to the naming test -> guard
exit 1 for no lookups; nonexistent explicit binaries -> go test fails
and the guard passes that through; the .exe suffix dropped ->
TestE2EDistNameMatchesBuildSh fails. actionlint is clean. Nothing here
has run on Windows; only a windows-e2e run shows that the suite passes
there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013WM5HyhwRLKBDyHR6qitkk
The first windows-e2e run of the agent E2E suite passed all but four
subtests, each one a test assuming the server side was Unix:

- exec with a nonzero exit read back 1, not 42. The server runs exec
  commands through its user's shell, which on Windows is
  `pwsh -Command`; that turns a failing native command's exit code into
  1, so `sh -c 'exit 42'` could never report 42. On Windows the test now
  sends `exit 42`, which PowerShell exits 42 on.
- The shell channel saw its marker echoed but never exited: the test
  typed Enter as \n. A Unix pty maps a terminal's \r to \n, so that
  worked there; the Windows server's ConPTY does not, and PowerShell took
  \n as Ctrl+Enter, a new line rather than a submitted command, so
  "exit" sat unrun until the 30 s frame read timed out. Enter is now \r
  everywhere, which is what a real terminal (the app's xterm.js
  included) sends.
- Two SFTP mode checks read back 0666 for 0640 and 0600. Windows has no
  Unix permissions; Go's os.Chmod there only toggles the read-only
  attribute from the owner write bit. servedFileMode says what a server
  on this platform reports; the Linux job still checks the exact bits.

Verified on linux/amd64: the agent E2E suite through
e2e/require-agent-e2e-ran.sh passes (26 lookups resolved, none skipped),
so \r works through a Unix pty; GOOS=windows go vet is clean. The
Windows-only branches are proven only by windows-e2e itself.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013WM5HyhwRLKBDyHR6qitkk

Copy link
Copy Markdown
Owner Author

dotnet-android-e2e failed on aa271c7 in run 36134733383. It is not caused by this PR.

  • The failure: the Android probe's SSH session was faulted with channel data is not being consumed fast enough.
  • Why it's not this PR: the same job passed on the same commit in the sibling run, 36134756955. This PR changes only Go E2E tests, CI YAML, .gitattributes and CLAUDE.md. None of those are used by the .NET library or the probe app.
  • Root cause: AgentChannelDataPump caps each channel's queue at 32 items, not bytes. A burst of small PTY output frames overflows it before the consumer catches up. The probe's own comment already describes working around this. The same thing can fail a real app session during a burst of terminal output.
  • Fix: it will come as a separate PR. The queue will be bounded by bytes (about 1 MiB), with a high item cap, which keeps the stalled-consumer memory bound. No fix exists yet, so nothing is ported here.
  • Re-run: 67a9e76, pushed for the Windows E2E fixes, re-runs this job anyway, so I'm not re-running it separately.

Generated by Claude Code

@Sniperlyf3
Sniperlyf3 merged commit bb55127 into main Sep 25, 2026
22 checks passed
@Sniperlyf3
Sniperlyf3 deleted the claude/meowsshapi-prs-issues-k2bx2m branch September 25, 2026 16:39
Sniperlyf3 added a commit that referenced this pull request Sep 25, 2026
A healthy shell could be failed with "channel data is not being consumed
fast enough". AgentChannelDataPump queued at most 32 items per channel,
whatever their size, so a burst of small PTY output frames (a prompt
redraw, a line-at-a-time command) filled it before the pump caught up.
dotnet-android-e2e hit exactly that on #60: the probe's SSH session was
faulted on a commit whose sibling run passed the same job. The bound is
there to cap memory held for a consumer that has stopped, and 32 items
capped that at a few hundred bytes for small frames while allowing 1 MiB
for 32 KiB ones.

The queue is now unbounded as a channel and bounded by what it holds: at
most 4 MiB of payload, plus a 16384-item cap so a flood of empty frames
cannot grow the queue's own overhead. Bytes are released once an item is
delivered, not when it is dequeued, since the item handed to a stalled
sink is still in memory. An empty queue always takes the next item, so a
single frame larger than the limit is never mistaken for backpressure.

Verified: dotnet test dotnet/Meowshell.sln 227/227. New tests: a burst of
4096 three-byte frames into a stalled sink does not fault; a frame over
the limit is accepted into an empty queue; a consumer that keeps up
receives 3x the limit without faulting. Each rule was mutated and a test
failed: item cap back to 32 (burst test), no empty-queue exemption
(oversize test), no byte limit (stall test), bytes never released
(running-total test). The two existing stall tests now overflow by bytes.


Claude-Session: https://claude.ai/code/session_013WM5HyhwRLKBDyHR6qitkk

Co-authored-by: Claude <noreply@anthropic.com>
Sniperlyf3 added a commit that referenced this pull request Sep 25, 2026
Five E2E test classes each carried the same binary lookup, and each
returned null -- "no binaries, skip" -- when a DOTNET_E2E_* variable was
set but named a file that did not exist. The tests treat null as a local
run and return early, so a mistyped or not-yet-built path in CI turned
the whole real-binary suite into a green no-op. That is the trap the Go
agent E2E suite fell into on Windows until #60, and CLAUDE.md listed the
.NET half as still open.

The five copies now share E2EBinaries.Sources(): both variables unset
still skips, as a local run should; one set without the other, or either
naming a missing file, throws with the variable and path. CLAUDE.md says
so in place of the "still goes quiet" note.

Verified: E2EBinariesTests pin all three outcomes. dotnet test
dotnet/Meowshell.sln passes 231/231 with the variables unset, and 231/231
against the real linux_amd64 binaries with e2e/start-testderp.sh running,
as CI runs it. With DOTNET_E2E_MEOWSHELL_BIN pointed at a missing file,
26 real-binary tests fail with "does not exist"; before this change the
same configuration passed.


Claude-Session: https://claude.ai/code/session_013WM5HyhwRLKBDyHR6qitkk

Co-authored-by: Claude <noreply@anthropic.com>
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.

2 participants