keepalive_connection_survives_with_pong_responses set ping_ms=100,
timeout_ms=250, so the server's pong-deadline fired ~560ms after the
first ping — only ~60ms past the end of the test's 500ms await window.
Under CI scheduler jitter that 60ms slack was insufficient and the
server timer fired inside the test window, closing the connection
mid-await and producing a flake.
Bump timeout_ms to 2000ms so the pong-deadline cannot fire within
the test window under any realistic jitter. ping_ms stays at 100ms
so the test still exercises multiple ping/pong rounds in the same
wall-clock budget.
Test still passes locally; was hitting 964's merge gate as a flake.
Two stories landed at the merge gate today (961, 962) with everything
else green, killed by a single missing `///` doc comment. The merge
gate runs source-map-check; script/check (and therefore the
mcp__huskies__run_check tool that coders use during iteration) did
not. So coders only saw the failure when the merge gate fired —
minutes after they thought they were done.
Chain source-map-check after cargo check so every iteration loop
catches a missing `///` instantly. AGENT.md already calls this out as
a required pre-commit step; making it part of the fast feedback loop
removes the "I forgot" failure mode.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two stories today (961, 962) passed every other gate and got bounced
at the merge step on a single missing `///` on a `pub mod` line.
Sonnet keeps treating the doc comment as optional when the rule says
"add doc comments to new modules and pub functions/structs/enums."
Promote the rule to its own loud section with no-exceptions wording
and a concrete reminder to run source-map-check before committing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reorders the gate so fmt --check, duplicate-module scan, clippy, and
doc-coverage run before the frontend build and the multi-minute test
suites. set -euo pipefail short-circuits on the first failure, so a
fmt or clippy drift now fails in seconds instead of after a 30s
frontend build.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The "living document" rule was soft and got ignored — coders wrote
PLAN.md once at session start and then drifted away from it. Tie the
update to a trigger they already do (the wip/final commit), and call
out stale "Current state" as a process failure.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The function was calling `read_content(story_id)`, which returns the
story's *description* text (e.g. "Bug: Coder exits code 0 with
uncommitted work — force a commit-only respawn..."). It then scanned
that for "Merge conflict" / "CONFLICT (content):", which obviously
never matched, so the auto-spawn-mergemaster-on-content-conflict guard
in `pool/auto_assign/merge.rs` always saw `false` and skipped.
The actual gate output (where the merge runner stores the failure
message including conflict markers) lives at
`format!("{story_id}:gate_output")` — that's the key
`pipeline/advance/mod.rs:207` writes to. Read from there instead.
Witnessed: 954's merge hit a real `CONFLICT (content)` in
tests_regression.rs at 08:57:40, no mergemaster spawned, story stayed
in MergeFailure.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The 5-line spread of `.unwrap_or_else(|| { ... })` in spawn.rs (from
the bd517f28 + 65416476 warm-resume work) doesn't match rustfmt's
preference for the short form. Was blocking every merge gate since
the warm-resume fix landed.
Follow-up to bd517f28. When --resume succeeds, claude-code restores the
full prior conversation — the agent already has its file reads, tool
results, and reasoning in context. Telling it to "read PLAN.md" forces
a redundant tool call to re-read a doc it wrote itself. PLAN.md is the
cold-start orientation doc (driven by AGENT.md); the resume -p prompt
should just be a continuation nudge.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
respawns can actually warm
claude-code's --resume <session_id> requires either:
a) a deferred-tool marker in the resumed session (i.e. the prior
session paused mid-tool-call), or
b) a non-empty -p prompt to continue the conversation with.
Watchdog-killed sessions have neither: the kill is asynchronous and
leaves no deferred-tool marker, and our harness was passing an empty
-p (because `resume_context_owned` is None for the common respawn
case). claude-code then aborts with:
"Error: No deferred tool marker found in the resumed session.
Either the session was not deferred, the marker is stale (tool
already ran), or it exceeds the tail-scan window. Provide a
prompt to continue the conversation."
The harness sees an aborted CLI with no session, prunes the recorded
session_id, and respawns cold — paying the full prompt-cache miss for
EVERY respawn. The new session_store logging (commit 0b50a624) made
this 100% legible: every warm spawn we observed went `mode=warm` →
crash → prune → `mode=cold` within a couple of seconds.
Fix: when resuming with no failure-context to send, default the -p
prompt to a brief "continue from PLAN.md" line. claude-code now has a
valid continuation message and warm-resume should actually work.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Helps explain WHY each spawn goes warm vs cold. The existing
`spawn mode=warm|cold` log only shows the outcome at the spawn point —
to count where warmth is being lost, we need to see:
- when a session_id is recorded (and for which key),
- what every lookup returns (key + Some/None),
- when remove_sessions_for_story prunes (which is currently the only
explicit cold-induction path beyond "first ever spawn").
After this lands a grep of "session_store" in the logs gives the full
warm-resume health picture: which (story,agent,model) triples have a
recorded session, which lookups are hitting it, and which prunes are
costing us a warm respawn.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PLAN.md is supposed to be a per-worktree planning file written by coder
agents and gitignored at the project root (.gitignore line 21, added by
952). But two recent merges shipped it anyway (945, 919) because the
squash-merge pipeline doesn't filter gitignored paths from the feature
branch diff — and once tracked, .gitignore stops protecting it.
This commit just removes it from master's tree. The structural fix
(squash-merge respects root .gitignore) is filed as a separate bug. If
an in-flight feature branch commits PLAN.md before that lands, this
file will be back on master at the next merge.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Without this, the only way to tell whether a watchdog-respawn went warm
(--resume <session_id>) vs cold (fresh CLI invocation) was to read the
args list of the existing "Spawning claude with args:" log and check
whether --resume was present. That made it impossible to count
cold-paths or distinguish "supposed-to-be-warm but resume_failed
fallback" from "first session" without source-diving.
This adds one slog! per spawn, prefixed `[agent:{sid}:{name}] spawn
mode=warm|cold session_id=...`, so grep "spawn mode=" answers it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The 12 tests in `agents::pool::pipeline::merge::tests` share a
process-wide `server_start_time` (a `OnceLock` captured the first time
the merge subsystem runs) and the global merge-job CRDT log. Default
cargo parallelism has caught at least one interleaving on the merge
gate's Docker scheduler where `stale_running_merge_job_is_cleared_and_retry_succeeds`
flakes — `delete_merge_job` from one test lands while another is mid-
assertion. Couldn't reproduce locally despite many tries.
Each test now acquires a poison-tolerant `std::sync::Mutex` at entry,
so the 12 tests run serially relative to each other while the rest of
the suite (2862 tests) stays parallel. Module-level
`#![allow(clippy::await_holding_lock)]` covers the deliberate sync
guard across `.await`s.
Targeted isolation — not a global `--test-threads=1`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>