Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions ops/cargo.sh
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,33 @@ toolchain_home="${RELAYFLOWS_TOOLCHAIN_HOME:-$HOME/.relayflows-toolchain}"
export CARGO_HOME="$toolchain_home/cargo"
export RUSTUP_HOME="$toolchain_home/rustup"

# Build OUTPUT must stay out of the propagated tree too, for a different and
# sharper reason than the toolchain above.
#
# `kernel/target/debug` is about 4900 files. With it inside the tree, the
# relayfile mount flush is rejected as too large and the run keeps going:
# relayfile flush failed after the command succeeded (exit 0); a later agent
# step may see stale files: ... notify flush: ... http 413 payload...
# The failure is non-fatal, so the workflow reports success while later steps
# read stale files and the delivered patch silently loses the run's real work.
# Three runs lost their work exactly this way (fdb49a9c 4127 changed files,
# ad98c2c3 4175, 52fa0752 3247 — three 413s each), against one that did not
# (76a4a8d1, 489 files, no flush failure). Of 52fa0752's changed paths, 4914
# matched target/debug and 1 matched node_modules, so this directory is the
# whole of the problem.
#
# Anything that hardcodes kernel/target/debug must read RELAYFLOWD_BIN instead;
# sdk/tests/live-kernel.test.ts already does.
# Keyed per worktree. Review caught that a single shared target dir would be
# used by every worktree under the same HOME (PR #38): cargo locks it, so the
# builds are safe, but two different source trees sharing one target thrash
# each other's artifacts and serialise behind the lock. The key is a hash of
# the worktree's own path, so isolation is automatic and needs no bookkeeping.
_worktree_root=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd -P)
_worktree_key=$(printf '%s' "$_worktree_root" | cksum | cut -d' ' -f1)
export CARGO_TARGET_DIR="${CARGO_TARGET_DIR:-$toolchain_home/target/$_worktree_key}"
export RELAYFLOWD_BIN="${RELAYFLOWD_BIN:-$CARGO_TARGET_DIR/debug/relayflowd}"

if command -v cargo >/dev/null 2>&1; then
cargo_bin=cargo
elif [ -x "${CARGO_INSTALL_ROOT:-}/bin/cargo" ]; then
Expand Down
38 changes: 34 additions & 4 deletions sdk/tests/live-kernel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
writeFileSync,
readFileSync,
} from 'node:fs';
import { tmpdir } from 'node:os';
import { homedir, tmpdir } from 'node:os';
import { dirname, join, resolve } from 'node:path';
import { spawn, spawnSync, type ChildProcess } from 'node:child_process';
import { fileURLToPath } from 'node:url';
Expand All @@ -22,9 +22,39 @@ const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..');
const SDK = join(ROOT, 'sdk');
const BUILT_CLI = join(SDK, 'dist', 'cli.js');
const TESTDATA = join(ROOT, 'testdata');
const RELAYFLOWD = resolve(
process.env['RELAYFLOWD_BIN'] ?? join(ROOT, 'kernel', 'target', 'debug', 'relayflowd'),
);
// ops/cargo.sh builds into a target dir OUTSIDE the repo, because
// kernel/target/debug is ~4900 files and its presence in the propagated tree
// makes the sandbox's relayfile flush fail with HTTP 413 — non-fatally, so runs
// silently lose their work. Resolution has to follow the build, or these cases
// SKIP rather than fail and the suite reports a false green.
const TOOLCHAIN_TARGET =
process.env['CARGO_TARGET_DIR'] ??
join(process.env['RELAYFLOWS_TOOLCHAIN_HOME'] ?? join(homedir(), '.relayflows-toolchain'), 'target');
const RELAYFLOWD = resolve(process.env['RELAYFLOWD_BIN'] ?? locateRelayflowd());

/**
* ops/cargo.sh builds into a target dir outside the repo, keyed per worktree so
* concurrent worktrees do not share one target. Resolution has to find that
* key without duplicating the hash, or these cases SKIP instead of failing and
* the suite reports a false green — and worse, a stale binary left at an older
* path gets exercised in place of the one just built.
*/
function locateRelayflowd(): string {
const direct = join(TOOLCHAIN_TARGET, 'debug', 'relayflowd');
if (existsSync(direct)) return direct;

// One level down: $toolchain/target/<worktree-key>/debug/relayflowd. Pick the
// most recently built, which is the one this checkout just produced.
const keyed = existsSync(TOOLCHAIN_TARGET)
? readdirSync(TOOLCHAIN_TARGET)
.map((entry) => join(TOOLCHAIN_TARGET, entry, 'debug', 'relayflowd'))
.filter((candidate) => existsSync(candidate))
.sort((a, b) => statSync(b).mtimeMs - statSync(a).mtimeMs)
: [];
if (keyed[0] !== undefined) return keyed[0];

return join(ROOT, 'kernel', 'target', 'debug', 'relayflowd');
}
const temporaryDirectories: string[] = [];
const daemons: ChildProcess[] = [];
const clients: JournalClient[] = [];
Expand Down