Skip to content

fleet spawn: --task brief arrives with its beginning cut off (422-char task, 13.1.5 broker) - #1943

Closed
agent-relay-code[bot] wants to merge 1 commit into
mainfrom
relayflow/relay-software-garden-cebc91e7
Closed

agent-relay-code[bot] wants to merge 1 commit into
mainfrom
relayflow/relay-software-garden-cebc91e7

Conversation

@agent-relay-code

@agent-relay-code agent-relay-code Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Fix fleet startup task truncation for Claude and Codex

Fleet-spawned agents could receive an initial brief with its beginning missing because the broker typed it into a composer that was still starting. Claude and Codex now receive the complete initial task as one native startup prompt argument, including resumed Codex sessions and explicit PTY harness configs. The broker retains its existing startup-task ordering and suppresses duplicate PTY injection.

  • Preserve leading/trailing whitespace in fleet task payloads.
  • Separate prompts from options with --, preserving leading dashes and protecting against variadic CLI options.
  • Confirm readiness when a native startup turn is already working; keep trust/auth dialog vetoes.
  • Reject NUL-containing or over-16-KiB argv prompts before registering an identity.
  • Extend the existing loopback fleet fixture with Claude/Codex 400-character and 8-KiB tasks, exact first-turn comparisons, delayed startup, busy readiness, and checks for duplicate PTY input.

Validation:

  • cargo build -p agent-relay-broker --bin agent-relay-broker — passed.
  • node_modules/.bin/tsc -p tests/integration/broker/tsconfig.json — passed.
  • node --test tests/integration/broker/dist/fleet-spawn-readiness.test.js — 7 passed, none skipped.
  • cargo test -p agent-relay-broker --lib native_startup -- --test-threads=1 — 5 passed.
  • cargo test -p agent-relay-broker --lib -- --test-threads=1 — 1,412 passed, 5 ignored, no failures.
  • cargo clippy -p agent-relay-broker --lib -- -D warnings — passed.
  • cargo clippy -p agent-relay-broker --all-targets -- -D warnings -A clippy::large_enum_variant -A clippy::result_large_err -A clippy::sliced_string_as_bytes — passed. Unadjusted strict all-target Clippy reports seven existing test-code lints under Rust 1.99.0.
  • cargo fmt --all --check, Prettier checks for the changed TypeScript/changelog, and git diff --check — passed.

Rust commands used CARGO_PROFILE_DEV_DEBUG=0 CARGO_PROFILE_TEST_DEBUG=0. For the full suite, inherited GIT_CONFIG_COUNT, GIT_CONFIG_KEY_0..3, and GIT_CONFIG_VALUE_0..3 were unset for the test process: this session's core.hooksPath=/dev/null override otherwise disables hooks in four existing hook-chain tests.

The 400-byte regression fails against the installed unmodified broker because its first startup turn is absent. Integration tests use the real broker/PTY and deterministic harness fixtures; no authenticated provider calls or running fleet services were used. Existing brokers need an upgrade and restart to use this path. No workflow files changed.

Checks

The checks failed, and there was no time left in the run to check the base commit (base not checked), so it is not known whether this change caused them. This pull request is a draft until someone looks.

What ran (.relayflow/check.sh)
```text
#!/bin/sh
# Fresh-machine equivalent of this repo's CI checks.
#
# Mirrors the jobs that run on the `trunk` -> `main` pull request and on pushes
# to `main`:
#   - .github/workflows/test.yml      (test + lint jobs)
#   - .github/workflows/codegen-models.yml (generated models in sync)
#   - .github/workflows/rust-ci.yml   (cargo test/fmt/clippy — only if a Rust
#                                      toolchain is present on this machine)
# AGENTS.md also asks for "the typecheck, tests and lint this repo uses" before
# a PR is labelled `mergeable`; `npm run build` covers the typecheck surface
# (every package is compiled with tsc).
set -e

cd "$(dirname "$0")/.."

# Every CI workflow sets this; keeps the suite from emitting telemetry.
export AGENT_RELAY_TELEMETRY_DISABLED=1
export CI=1

# --- Dependencies -----------------------------------------------------------
# Same install CI uses. There are no install lifecycle hooks in this repo, so
# this needs only the npm registry.
npm ci

# CI re-adds rollup because npm can skip its platform-specific optional
# dependency. Non-fatal there, non-fatal here.
npm install --no-save rollup || true

# --- Build / code generation ------------------------------------------------
# `npm test` would build via its `pretest` hook, but CI (node-compat.yml,
# e2e-tests.yml) builds explicitly first and it keeps a build break
# distinguishable from a test failure.
#
# `build:rust` inside this script is itself conditional: with no cargo on PATH
# it prints a notice and skips, and the TS suite then runs against the broker
# binary from the platform optional dependency. The Rust crates are still
# checked below when a toolchain exists.
npm run build

# Regenerate the models derived from packages/utils/cli-registry.yaml. Reads
# local YAML only — no network. codegen-models.yml fails a PR when the
# committed output drifts, so assert the tree is unchanged.
npm run codegen:models
git diff --exit-code \
  packages/config/src/cli-registry.generated.ts \
  packages/sdk-py/src/agent_relay/models.py

# --- Tests ------------------------------------------------------------------
# Order matches the `test` job in test.yml.
node --test \
  scripts/pr-proof/broker-transfer.test.mjs \
  scripts/pr-proof/run-cloud.test.mjs \
  scripts/flows/deploy-listeners.test.mjs \
  scripts/pr-proof/event-from-input.test.mjs

npm run test:subscriptions:proof

# This is `npm test` (vitest run), invoked directly so the `pretest` hook does
# not clean and rebuild everything a second time.
npx vitest run

# --- Lint / formatting (test.yml `lint` job) --------------------------------
npm run lint

# Formatting is reported but not fatal: prettier-fmt-fix.yml runs `npm run
# format` on the PR and pushes the fix back, so drift is bot-maintained rather
# than something a local run must satisfy. The baseline already has drift
# (AGENTS.md, two workflow files at the time of writing), which would otherwise
# fail this script for reasons unrelated to the change under test.
npm run format:check || echo 'check.sh: prettier reported formatting drift (non-fatal; see prettier-fmt-fix.yml)'

# --- Rust (rust-ci.yml) -----------------------------------------------------
# Skipped entirely when no Rust toolchain is installed; `npm ci` does not
# install one and CI gets it from dtolnay/rust-toolchain.
if command -v cargo >/dev/null 2>&1; then
  cargo test
  cargo fmt -- --check
  cargo clippy -- -D warnings
fi

```

_…truncated to fit GitHub's limit: 4369 bytes were cut to under 4000. The full text is .relayflow/check.sh in the run workspace._
Output on this branch (last 80 lines)
�[41m�[1m FAIL �[22m�[49m packages/cli/src/cli/lib/node-claim.test.ts�[2m > �[22mclaim hold descriptor�[2m > �[22mstill frees a node whose recorded child is gone and only a helper holds the fence
�[31m�[1mAssertionError�[22m: expected { state: 'held', …(2) } to match object { state: 'stale' }
(14 matching properties omitted from actual)�[39m

�[32m- Expected�[39m
�[31m+ Received�[39m

�[2m  {�[22m
�[32m-   "state": "stale",�[39m
�[31m+   "state": "held",�[39m
�[2m  }�[22m

�[36m �[2m❯�[22m packages/cli/src/cli/lib/node-claim.test.ts:�[2m1372:78�[22m�[39m
    �[90m1370|�[39m
    �[90m1371|�[39m     �[35mtry�[39m {
    �[90m1372|�[39m       await expect(inspectNodeClaim('node_1', { env, execCommand: real…
    �[90m   |�[39m                                                                              �[31m^�[39m
    �[90m1373|�[39m         state�[33m:�[39m �[32m'stale'�[39m�[33m,�[39m
    �[90m1374|�[39m       })�[33m;�[39m

�[31m�[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[9/12]⎯�[22m�[39m

�[41m�[1m FAIL �[22m�[49m packages/cli/src/cli/lib/node-claim.test.ts�[2m > �[22mclaim hold descriptor�[2m > �[22mprunes a superseded generation together with its hold file
�[31m�[1mAssertionError�[22m: expected true to be false // Object.is equality�[39m

�[32m- Expected�[39m
�[31m+ Received�[39m

�[32m- false�[39m
�[31m+ true�[39m

�[36m �[2m❯�[22m packages/cli/src/cli/lib/node-claim.test.ts:�[2m1453:60�[22m�[39m
    �[90m1451|�[39m
    �[90m1452|�[39m     �[34mexpect�[39m(second�[33m.�[39mgeneration)�[33m.�[39m�[34mtoBe�[39m(�[34m2�[39m)�[33m;�[39m
    �[90m1453|�[39m     �[34mexpect�[39m(fs�[33m.�[39m�[34mexistsSync�[39m(�[34mnodeClaimPath�[39m(�[32m'node_1'�[39m�[33m,�[39m env�[33m,�[39m �[34m1�[39m)))�[33m.�[39m�[34mtoBe�[39m(�[35mfalse�[39m)�[33m;�[39m
    �[90m   |�[39m                                                            �[31m^�[39m
    �[90m1454|�[39m     // Left behind, the spent hold file would keep answering for a gen…
    �[90m1455|�[39m     �[90m// that no longer owns anything.�[39m

�[31m�[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[10/12]⎯�[22m�[39m

�[41m�[1m FAIL �[22m�[49m packages/cli/src/cli/lib/node-claim.test.ts�[2m > �[22mclaim hold descriptor�[2m > �[22mfrees the node id as soon as the orphaned child is gone
�[31m�[1mAssertionError�[22m: expected 'held' to be 'stale' // Object.is equality�[39m

Expected: �[32m"stale"�[39m
Received: �[31m"held"�[39m

�[36m �[2m❯�[22m packages/cli/src/cli/lib/node-claim.test.ts:�[2m1474:26�[22m�[39m
    �[90m1472|�[39m     const status = await inspectNodeClaim('node_1', { env, execCommand…
    �[90m1473|�[39m
    �[90m1474|�[39m     �[34mexpect�[39m(status�[33m.�[39mstate)�[33m.�[39m�[34mtoBe�[39m(�[32m'stale'�[39m)�[33m;�[39m
    �[90m   |�[39m                          �[31m^�[39m
    �[90m1475|�[39m   })�[33m;�[39m
    �[90m1476|�[39m

�[31m�[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[11/12]⎯�[22m�[39m

�[41m�[1m FAIL �[22m�[49m packages/cli/src/cli/lib/node-definition-loader.bun.test.ts�[2m > �[22mnode definition loading from a bun-compiled binary�[2m > �[22mserves a CommonJS .cts config through the transpiling Node loader
�[31m�[1mError�[22m: Command failed: bun build --compile --minify /tmp/relay-bun-loader-wOmbW3/describe-harness-agent-relay-bare-cts.ts --outfile /tmp/relay-bun-loader-wOmbW3/describe-harness-agent-relay-bare-cts
Error writing ELF file: ENOSPC: No space left on device (write())
failed to write compiled executable /tmp/relay-bun-loader-wOmbW3/describe-harness-agent-relay-bare-cts
�[39m
�[36m �[2m❯�[22m compile packages/cli/src/cli/lib/node-definition-loader.bun.test.ts:�[2m47:3�[22m�[39m
    �[90m 45|�[39m   �[34mwriteFileSync�[39m(entry�[33m,�[39m source�[33m,�[39m �[32m'utf8'�[39m)�[33m;�[39m
    �[90m 46|�[39m   �[35mconst�[39m outfile �[33m=�[39m path�[33m.�[39m�[34mjoin�[39m(dir�[33m,�[39m name)�[33m;�[39m
    �[90m 47|�[39m   execFileSync('bun', ['build', '--compile', '--minify', entry, '--out…
    �[90m   |�[39m   �[31m^�[39m
    �[90m 48|�[39m     stdio�[33m:�[39m �[32m'pipe'�[39m�[33m,�[39m
    �[90m 49|�[39m   })�[33m;�[39m
�[90m �[2m❯�[22m describeViaHarness packages/cli/src/cli/lib/node-definition-loader.bun.test.ts:�[2m358:18�[22m�[39m
�[90m �[2m❯�[22m packages/cli/src/cli/lib/node-definition-loader.bun.test.ts:�[2m235:24�[22m�[39m

�[31m�[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[12/12]⎯�[22m�[39m


�[2m Test Files �[22m �[1m�[31m3 failed�[39m�[22m�[2m | �[22m�[1m�[32m194 passed�[39m�[22m�[2m | �[22m�[33m3 skipped�[39m�[90m (200)�[39m
�[2m      Tests �[22m �[1m�[31m12 failed�[39m�[22m�[2m | �[22m�[1m�[32m3694 passed�[39m�[22m�[2m | �[22m�[33m30 skipped�[39m�[90m (3736)�[39m
�[2m   Start at �[22m 00:11:19
�[2m   Duration �[22m 66.99s�[2m (transform 9.26s, setup 16.13s, import 41.85s, tests 111.60s, environment 24ms)�[22m

Fixes #1938

View guided diff


Agent Relay sessions

  • claude session af230368 · contributor · ran gh pr commands · last active 2026-10-10

@coderabbitai

coderabbitai Bot commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 90003b40-ba16-4583-a9bc-e45b9a80e1d6

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@khaliqgant

Copy link
Copy Markdown
Member

Triage after #1930 / v13.3.0: probably superseded. Leaving this open for Khaliq to decide.

The truncation this PR fixes, a fleet --task brief losing its start while the composer boots, is addressed on main by #1893 (merged via #1930, released in 13.3.0). On main, the broker waits for startup readiness before injecting, sends the task as one bracketed paste with a 14 KiB envelope cap, and confirms the task through harness acceptance. If acceptance fails, the spawn resolves as spawn_task_failed / spawn_task_unconfirmed instead of reporting success. This PR's 400-byte regression was measured against a pre-13.3.0 broker.

What this PR does differently, and why it collides with main:

  • It moves Claude and Codex initial tasks off the PTY onto an argv startup prompt. On main, a confirmed fleet spawn binds its initial PTY task to the pending spawn and resolves it on harness_acceptance (runtime/fleet.rs PendingVerifiedSpawn). An argv task would skip that, which is a design change, not a bug fix.
  • It conflicts with main in pty_worker.rs, worker.rs, runtime/fleet.rs and runtime/relaycast_events.rs. Promote #1939: verify PTY turn submission (#1891) + review fixes (#1954, #1955) #1959 is rewriting the same startup/acceptance paths right now.

What it would still add, if wanted as small separate changes:

  • action_invoke_task keeps the task's leading and trailing whitespace. Main's action_invoke_string trims it through non_empty.
  • When the harness is already running an argv startup turn, readiness can be proven from its busy line.

Recommendation: close this as superseded by #1893/#1930. If argv startup for Claude/Codex is still wanted, start it fresh after #1959 lands.

🤖 Generated with Claude Code

@khaliqgant

Copy link
Copy Markdown
Member

Closing as superseded: the #1893 fix (merged via #1930, shipped in v13.3.0) covers the truncated startup brief by waiting for terminal readiness, using a bracketed paste and requiring harness acceptance. Passing the task as a startup argument would bypass that confirmation. See #1943 (comment). The whitespace-preservation detail can come back as a small follow-up if needed.

@khaliqgant khaliqgant closed this Oct 10, 2026
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.

fleet spawn: --task brief arrives with its beginning cut off (422-char task, 13.1.5 broker)

1 participant