Repository navigation
test(e2e): exercise MCP logs cutoff and typed failures against real Compose - #341
Merged
Merged
Conversation
…ompose HACK-1134 (unit F13), HACK-1155 (P11). Add the docker-tier `mcp-logs-compose` scenario. It drives `hack mcp serve` over stdio against a real Compose project and checks the logs contract: complete, time cutoff (quiet and live), event cutoff, byte cutoff with a UTF-8-safe partial result, closed backend_error with an unreachable engine, a tail across a container restart, and no replay after reconnect. On the same stack it checks that `hack run <svc> -- false` / `exit 7` and the MCP `hack.project.run` envelope report the child's exit status. Add tests/run-child-exit-status.test.ts: the source CLI against a recording docker stub proves the exit status, argv after `--`, the pipe-filter negative control and the MCP envelope without a Docker engine. Update the HACK-P11 pattern card to cite the agent-surface-freeze negative control (#296) and narrow the missing contract; regenerate the plugin bundle. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The first hosted run replayed 50 lines, which included the older boot marker, so only 4 of the 5 counted log events were ticks. Replay 3 ticks and let 2 live ones reach the bound. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This comment has been minimized.
This comment has been minimized.
docker ps -q prints 12-character IDs, so the exact-ID check refused the ticker container before the restart step. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…state The hosted macOS test job printed CoreDNS/Caddy reachability warnings on hack run stdout, which the exact stdout assertion rejected. Turn internal DNS/TLS off in the fixture so no probe runs, and call /bin/bash directly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolve plugins/hack/skills/hack-cli/references/patterns.md by regenerating it (bun run generate:agent-plugins) from the merged src/agents/pattern-catalogue.ts, which carries both this branch's HACK-P11 wording and the pattern changes from #336. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This comment has been minimized.
This comment has been minimized.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
HACK-1134 (unit F13) and HACK-1155 (P11). Test and guidance change only; no product code changes.
Final behavior
mcp-logs-compose(docker tier, optionaldocker-e2ejob)The scenario starts
hack mcp serveover stdio with the MCP SDK client. Every tool call runs the CLI under test throughHACK_MCP_COMMAND(the compileddist/hackin CI). The project is a real Compose project:alpine:3.20withpull_policy: nevercomposeIt has three services:
tickerprints a boot marker, then a numbered line every 0.2 s.quietprints one line.bulkprints 1500 lines of 2048 four-byte emoji (about 12 MiB), then idles.The scenario requires these results:
quietsnapshot:ok,cutoff: null, exit 0, its one line and anendevent with reasoneofquiettail withmaxMs: 2000:{bound: "time", limit: 2000},stop_reason: timeout, only the one replayed linetickertail withmaxMs: 3000:timecutoff, at least 5 live lines in strict order,duration_msfrom 3000 up to 30000tickertail withmaxEvents: 5:{bound: "events", limit: 5}, exactly 5 log events,stop_reason: max_eventsbulksnapshot:{bound: "bytes", limit: 8388608},outputTruncated, an ordered prefix of whole 2048-emoji lines (line 1 onward, noBULK-DONE)DOCKER_HOSTset to a missing socket. Snapshot and tail both returnok: false,isError, exactly the closed{E_UNEXPECTED, backend_error, fixed message}andcutoff: null. The tail ends ateofwell beforemaxMsdocker restarton the exact ownedtickercontainer during a 10 s tail. The tail must end in one typed outcome (time cutoff, complete or backend_error) within its bound, with at least 2 ordered boot-1 lines. The container must keep its ID with a newStartedAt(positive control). The later history snapshot holdsBOOT 1thenBOOT 2once each, with strictly ordered lines. A new tail returns 3 lines, all from boot 2hack run quiet -- falseexits 1.sh -c 'printf p11-partial; exit 7'exits 7 with its output. The MCPhack.project.runresult hasok: false,isErrorandexitCode: 7. Atruechild givesok: true(positive control). No one-off container is left behindCleanup removes only containers, networks and volumes with the fixture's owner label. It is registered in
ALL_SCENARIOSwith an estimated 60 s shard hint.Lines repeated across a restart are logged, not asserted. Compose may reattach to the restarted container from a coarse timestamp, so this is backend behavior, not an MCP contract violation.
docs/reference/mcp-logs.mdnow says so.tests/run-child-exit-status.test.ts(requiredtestjob, no Docker)The source CLI runs with a private HOME and HACK_HOME (
privateCliEnv) against a recordingdockerstub. The stub playscompose runby running the argv after the service on the host. The test checks four things:hack run app -- falseexits 1 with no stdout.exit 7exits 7 and keeps its output.trueexits 0.--jsonafter--reaches the child unchanged (recorded argv).Pipe negative control in bash:
hack run … -- false | catgivespipeline=0 hack=1without pipefail andpipeline=1 hack=1with it. The filter hides the failure only from the pipeline, never from hack's own status.The in-process MCP
hack.project.runenvelope reportsok: false/exitCode: 1,ok: false/exitCode: 7with stdoutpartial, andok: true/exitCode: 0.HACK-P11 pattern card
The card now cites the
agent-surface-freezeE2E negative control (#296) under cleanup. Readiness says thehack runexit status and the MCP run result'sexitCodeare the child's own. The missing contract is narrowed to the two items still open: a freeze that refuses explicit guidance updates while an agent runs, and a typed terminal result for resumed background operations. The plugin bundle is regenerated withbun run generate:agent-plugins. Onlyplugins/hack/skills/hack-cli/references/patterns.mdchanged. No integration-ownership fingerprint or instruction content revision changed: the catalogue is not a fingerprinted surface, and those tests pass.Validation (local, macOS, Bun 1.4.3, no Docker)
bun test tests/run-child-exit-status.test.ts: 3 pass, 3 consecutive runs. An earlier flaky 143 was Bun's 5 s default test timeout killing the CLI. Each test now sets a 90 s timeout.bun test tests/pattern-catalogue.test.ts tests/pattern-examples.test.ts tests/integration-ownership.test.ts tests/agent-instruction-source.test.ts tests/agent-plugin-generation.test.ts tests/cli-spawn-env-isolation.test.ts tests/e2e-shard.test.ts: 78 pass, 0 fail.bun run --cwd packages/cli typecheck: exit 0.bun run --cwd packages/cli check: 0 errors, 34 warnings, the same as the base.bunx ultracite checkon the changed files: clean.bun tests/e2e/run.ts --listlistsmcp-logs-compose. The shard partition test passes.bun run privacy:check: ok.HACK_MCP_COMMANDstub. The realhack mcp servereturned the byte cutoff (506 whole lines, about 50 MB result over stdio), an events cutoff and a closed backend_error. This run found and fixed a non-uemoji regex in the scenario.Hosted docker-e2e (real Compose)
mcp-logs-composeran ondocker-e2e shard 4/4(Blacksmith Ubuntu 24.04, compileddist/hack).706e9567: PASS in 25.0 s (job). The requiredtestjob also passed on this head (job). The scenario is unchanged since889cd62a.889cd62a: PASS in 25.3 s (job). Observed results:timecutoff at 2009 ms with 1 line.timecutoff at 3006 ms with 15 lines. Event tail: exactly 5 log events (3 replayed + 2 live) in 521 ms.bytescutoff with 501 whole lines and 8388606 stdout bytes kept, no U+FFFD.backend_error(taileofat 259 ms).timecutoff, boots 1 and 2 seen, no repeated lines this run. History holdsBOOT 1thenBOOT 2.hack.project.run:exit 7gaveok: false/exitCode: 7, andtruegaveok: true.22f1d477: the event tail replayed 50 lines including the older boot marker, so only 4 of the 5 counted events were ticks. It now replays 3.711e29dd:docker ps -qprinted short IDs, which the exact-ID check rejected. It now uses--no-trunc.Hosted
testjob (macOS)The
testjob failed on889cd62awith 207 failures. Two of them were in the new P11 test:hack runstdout carried CoreDNS/Caddy reachability warnings. The fixture now turns internal DNS/TLS off, so no probe runs.bashwas missing from PATH. The test now calls/bin/bash.The rest of the 207 were a suite-wide environment cascade.
bun,bashanddockerwere missing from PATH, and HOME-based paths were wrong. It started with the known nondeterministicnative-authored-project-startfailure ("Startup fixture settlement unconfirmed; roots retained"), so it is not attributed to this change.testpassed on706e9567.Finding, not fixed here:
hack runwrites those Hack warnings to stdout, mixed with the child's output. That is a P11 diagnostics/results separation gap for HACK-1155.Negative controls (mutations, each restored)
composeRuntimeBackend.runcollapses any non-zero status to 1: all 3 P11 tests fail (exit 7 read as 1).composeRuntimeBackend.runreturns 0: all 3 fail.--jsonfrom the child argv: the argv test fails.buildToolResultusesok = exitCode < 2: the envelope test fails.codePointSafe: false: the bulk snapshot fails with "must return valid UTF-8". With the real collector it passes.Remaining gaps
tests/mcp-logs-contract.test.ts.hack runwrites Hack's own warnings to stdout (see above). This is not fixed here.hack setupduring execution, and a typed terminal result for resumed background operations, remain open.🤖 Generated with Claude Code