paperclipai/paperclip · warning · Error
Run log tail tick returned unparseable output.
Error message
Run log tail tick returned unparseable output.
What it means
Thrown by tick() after a successful (exit 0) tail command when the captured stdout does not contain the expected marker frame: a TAIL_MARKER_STDOUT line, a later TAIL_MARKER_STDERR line, and a later TAIL_MARKER_END line, in that order. It means the remote shell produced structurally unexpected output — e.g. a login banner, a different shell that does not support printf, or a partial capture cut between markers.
Source
Thrown at packages/adapter-utils/src/sandbox-run-log-stream.ts:183
}
}
async function tick(): Promise<void> {
const result = await options.runner.execute({
command: shellCommand,
args: shellCommandArgs(buildTickScript()),
cwd: options.remoteCwd,
env: { [SANDBOX_EXEC_CHANNEL_ENV]: SANDBOX_EXEC_CHANNEL_BRIDGE },
timeoutMs: tickTimeoutMs,
});
if (result.timedOut || (result.exitCode ?? 1) !== 0) {
throw new Error(
`Run log tail tick failed (exit ${result.exitCode ?? "null"}${result.timedOut ? ", timed out" : ""}).`,
);
}
const sections = parseTickOutput(result.stdout);
if (!sections) {
throw new Error("Run log tail tick returned unparseable output.");
}
await emitBytes(streams[0], sections.stdout);
await emitBytes(streams[1], sections.stderr);
}
async function loop(): Promise<void> {
let consecutiveFailures = 0;
while (!stopped) {
await sleep(pollIntervalMs);
if (stopped) break;
try {
await tick();
consecutiveFailures = 0;
} catch {
consecutiveFailures += 1;
if (consecutiveFailures >= maxConsecutiveFailures) {
degraded = true;
break;View on GitHub (pinned to 67001ec6eb)
Solutions
- Log result.stdout verbatim on parse failure to see what broke the marker frame.
- Ensure the remote login shell for the exec channel is POSIX (sh/bash) and non-interactive (no profile output to stdout).
- Increase tickTimeoutMs so the full marker frame is captured atomically.
- Confirm base64/tail/head exist on the remote host and the marker constants are not accidentally redefined.
Defensive patterns
Strategy: validation
Validate before calling
function validateTickFrame(stdout: string): void {
const lines = stdout.split(/\r?\n/);
const s = lines.indexOf(TAIL_MARKER_STDOUT);
const e = lines.indexOf(TAIL_MARKER_STDERR);
const end = lines.indexOf(TAIL_MARKER_END);
if (s < 0 || e < s || end < e) {
throw new Error(`tail frame malformed; first 200 bytes: ${stdout.slice(0, 200)}`);
}
} Try / catch
try { await tick(); } catch (err) { consecutiveFailures += 1; /* loop tolerates up to maxConsecutiveFailures */ } Prevention
- Use a non-interactive POSIX shell for the exec channel (no profile output to stdout).
- Keep tickTimeoutMs high enough to capture the full marker frame atomically.
- Confirm tail/head/base64/printf exist on the remote host.
When it happens
Trigger: parseTickOutput returns null because indexOf markers are missing or mis-ordered: stdoutIndex<0, stderrIndex<stdoutIndex, or endIndex<stderrIndex. Happens when remote shell is not POSIX sh (printf flags differ), a banner precedes the first marker, or stdout was truncated mid-frame.
Common situations: Remote default shell is fish/csh which mishandle the printf/head/base64 pipeline; .bashrc prints to stdout; tickTimeoutMs cut the output before TAIL_MARKER_END; a wrapper script injects text; locale/encoding corrupts the marker bytes.
Related errors
- Run log tail tick failed (exit ${result.exitCode ?? "null"}$
- Invalid integer value: ${value}
- Invalid integer value: ${value}
- Invalid JSON: ${err instanceof Error ? err.message : String(
- Invalid --name-override "${raw}". Use slug=name.
AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12).
Data as JSON: /api/errors/9eee19417e4cbb83.
Report an issue: GitHub.