paperclipai/paperclip · error

${input.label} sync wrote invalid result JSON: ${error insta

Error message

${input.label} sync wrote invalid result JSON: ${error instanceof Error ? error.message : String(error)}

What it means

Thrown by syncRemoteTextFileWithHashSkip after the remote upload script completed successfully (requireSuccessfulResult passed, exit code 0) but stdout could not be parsed as JSON with an uploaded field. The script prints {"uploaded":true} after a fresh upload, {"uploaded":false} after a sha256 short-circuit skip; any other stdout breaks JSON.parse and surfaces as this error with the parse failure attached.

Source

Thrown at packages/adapter-utils/src/sandbox-callback-bridge.ts:970

      "    exit 1",
      "  fi",
      "else",
      `  echo ${shellQuote(`${input.label} sha verify skipped: no sha256sum/shasum on remote.`)} >&2`,
      "fi",
      "mv \"$remote_partial\" \"$remote_path\"",
      "printf '{\"uploaded\":true}\\n'",
    ].join("\n"),
    timeoutMs,
    shellCommand,
    base64Body,
  );
  requireSuccessfulResult(input.action, syncResult);

  let uploaded = false;
  try {
    uploaded = JSON.parse(syncResult.stdout.trim())?.uploaded === true;
  } catch (error) {
    throw new Error(
      `${input.label} sync wrote invalid result JSON: ${error instanceof Error ? error.message : String(error)}`,
    );
  }

  return { uploaded, sha256 };
}

export async function syncSandboxCallbackBridgeEntrypoint(input: {
  runner: CommandManagedRuntimeRunner;
  remoteCwd: string;
  assetRemoteDir: string;
  bridgeAsset: SandboxCallbackBridgeAsset;
  timeoutMs?: number | null;
  shellCommand?: "bash" | "sh" | null;
}): Promise<{ remoteEntrypoint: string; sha256: string; uploaded: boolean }> {
  const remoteEntrypoint = path.posix.join(input.assetRemoteDir, SANDBOX_CALLBACK_BRIDGE_ENTRYPOINT);
  const entrypointSource = await fs.readFile(input.bridgeAsset.entrypoint, "utf8");
  const { uploaded, sha256 } = await syncRemoteTextFileWithHashSkip({

View on GitHub (pinned to 67001ec6eb)

Solutions

  1. Silence non-interactive shell noise on the remote: run bash --noprofile --norc -c '...' and remove stdout-echoing statements from .bashrc/.profile.
  2. Use the bridge's shellCommand option to force a clean shell ("bash" with --noprofile --norc, or "sh" for minimal remotes).
  3. Disable ssh MOTD/banner output for the runtime user (sshd config: PrintMotd no, Banner none; remove /etc/motd echoes).
  4. When debugging, log syncResult.stdout verbatim before JSON.parse — the raw text reveals the polluting line, which the wrapped error does not include.

Example fix

// before: remote /etc/profile echoes a welcome line to stdout
// echo "Welcome to $HOSTNAME"  # delete or redirect to stderr

// after: pass a clean non-interactive shell to the bridge
syncSandboxCallbackBridgeEntrypoint({
  runner, remoteCwd, assetRemoteDir, bridgeAsset,
  shellCommand: "bash",
});
// and ensure the runtime user's startup files are silent non-interactively
Defensive patterns

Strategy: validation

Validate before calling

async function preflightSyncShellIsSilent(input: { runner: CommandManagedRuntimeRunner; remoteCwd: string }): Promise<void> {
  const result = await runShell(input.runner, input.remoteCwd, 'printf \'{"uploaded":true}\\n\'', 5000, "bash");
  try {
    if (JSON.parse(result.stdout.trim())?.uploaded !== true) {
      throw new Error(`Unexpected stdout: ${result.stdout}`);
    }
  } catch (error) {
    throw new Error(`Remote sync shell pollutes stdout; disable MOTD/.bashrc echoes: ${String(error)}`);
  }
}

Try / catch

try {
  await syncSandboxCallbackBridgeEntrypoint(input);
} catch (error) {
  if (error instanceof Error && /sync wrote invalid result JSON/.test(error.message)) {
    throw new BridgeError("Sync remote shell wrote non-JSON to stdout. Suppress MOTD/.bashrc echoes or switch to bash --noprofile --norc.", { cause: error });
  }
  throw error;
}

Prevention

When it happens

Trigger: Remote shell prints a banner, trace, or warning to stdout before the printf; sha256sum or shasum on the remote emits output that escapes the script's redirection discipline; or a partial run left stdout truncated. The parse at sandbox-callback-bridge.ts:968 throws, which is wrapped with input.label (e.g. "Sandbox callback bridge entrypoint") for context.

Common situations: Same family as 337: SSH MOTD, .bashrc echoes, set -x shell tracing, or PAM modules writing to stdout. Specific to this code path: missing hash tools on minimal remotes (Alpine/scratch) trigger the sha-verify-skipped branch but still print the expected JSON, so that path alone does not cause this error; the cause is stdout pollution or a truncated shell result.

Related errors


AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12). Data as JSON: /api/errors/4c2d58a14bded9da. Report an issue: GitHub.