From 38d1eeede426c5e87e1410ef8d505f8b8dabf656 Mon Sep 17 00:00:00 2001 From: Bryan Johnson Date: Wed, 27 May 2026 12:20:20 -0700 Subject: [PATCH] v0.6.3: route all large jq inputs through tempfiles, not argv MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit v0.6.2 fixed the TOOLS_JSON argv overflow but four other call sites had the same risk pattern — any of them would have crashed under Cygwin's ~32KB argv cap with large user input, large agent responses, or large tool results: add_user_text --arg c "$content" ← multi-paragraph prompts add_assistant_blocks --argjson b "$blocks" ← long assistant turns add_user_tool_results --argjson b "$blocks" ← chained tool results agent_turn loop --arg c "$result" ← tool output (up to 250KB for read_file, 500 lines for ssh_exec, etc.) agent_turn loop --arg system "$system_prompt" ← agents/*.md total ~25KB All five are now passed via tempfile + --rawfile (for raw strings) or --slurpfile (for pre-parsed JSON). Same proven pattern as the v0.6.2 TOOLS_JSON fix. Tempfiles are cleaned at every return path. Verified by pushing a 60KB user prompt through the pipeline on macOS (also has the larger 256KB argv cap that masked these bugs locally before, but the codepath now uses files for the large values regardless of platform). Messages file stored the full 60025-char prompt with no warnings. After this commit, the only --arg / --argjson calls remaining all carry known-small values (UUIDs, version strings, port numbers, etc.). Co-Authored-By: Claude Opus 4.7 --- VERSION | 2 +- larry.sh | 56 +++++++++++++++++++++++++++++++++++++++----------------- 2 files changed, 40 insertions(+), 18 deletions(-) diff --git a/VERSION b/VERSION index b616048..844f6a9 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.6.2 +0.6.3 diff --git a/larry.sh b/larry.sh index ca5ec07..edb41c4 100755 --- a/larry.sh +++ b/larry.sh @@ -36,7 +36,7 @@ set -o pipefail # ───────────────────────────────────────────────────────────────────────────── # Config # ───────────────────────────────────────────────────────────────────────────── -LARRY_VERSION="0.6.2" +LARRY_VERSION="0.6.3" LARRY_HOME="${LARRY_HOME:-$HOME/.larry}" LARRY_BASE_URL="${LARRY_BASE_URL:-https://raw.githubusercontent.com/bojj27/cloverleaf-larry/main}" LARRY_UPDATE_URL="${LARRY_UPDATE_URL:-${LARRY_BASE_URL}/larry.sh}" @@ -457,23 +457,37 @@ log_append() { printf '%s\n' "$1" >> "$LOG_FILE"; } # On MobaXterm/Cygwin the bundled jq is a Windows-native binary that can't # resolve Cygwin paths like /home/mobaxterm/... when they come in as argv. # Stdin redirection always works because bash does the path open() itself. +# Each of these passes the value through a tempfile (--rawfile / --slurpfile) +# rather than argv (--arg / --argjson). Argv overflow ("Argument list too +# long") on Cygwin's ~32KB total cap was the v0.6.1 bug for TOOLS_JSON; the +# same pattern applies to any value that could grow with user input or +# assistant output (multi-paragraph prompts, large tool results, etc.). add_user_text() { local content="$1" - local tmp; tmp=$(mktemp) - jq --arg c "$content" '. + [{"role":"user","content":[{"type":"text","text":$c}]}]' < "$MESSAGES_FILE" > "$tmp" \ + local cfile tmp + cfile=$(mktemp); tmp=$(mktemp) + printf '%s' "$content" > "$cfile" + jq --rawfile c "$cfile" '. + [{"role":"user","content":[{"type":"text","text":$c}]}]' < "$MESSAGES_FILE" > "$tmp" \ && mv "$tmp" "$MESSAGES_FILE" + rm -f "$cfile" } add_assistant_blocks() { local blocks="$1" - local tmp; tmp=$(mktemp) - jq --argjson b "$blocks" '. + [{"role":"assistant","content":$b}]' < "$MESSAGES_FILE" > "$tmp" \ + local bfile tmp + bfile=$(mktemp); tmp=$(mktemp) + printf '%s' "$blocks" > "$bfile" + jq --slurpfile b "$bfile" '. + [{"role":"assistant","content":$b[0]}]' < "$MESSAGES_FILE" > "$tmp" \ && mv "$tmp" "$MESSAGES_FILE" + rm -f "$bfile" } add_user_tool_results() { local blocks="$1" - local tmp; tmp=$(mktemp) - jq --argjson b "$blocks" '. + [{"role":"user","content":$b}]' < "$MESSAGES_FILE" > "$tmp" \ + local bfile tmp + bfile=$(mktemp); tmp=$(mktemp) + printf '%s' "$blocks" > "$bfile" + jq --slurpfile b "$bfile" '. + [{"role":"user","content":$b[0]}]' < "$MESSAGES_FILE" > "$tmp" \ && mv "$tmp" "$MESSAGES_FILE" + rm -f "$bfile" } # ───────────────────────────────────────────────────────────────────────────── @@ -1040,17 +1054,19 @@ build_system_prompt() { # ───────────────────────────────────────────────────────────────────────────── agent_turn() { local system_prompt="$1" - # Write the TOOLS_JSON to a file ONCE per agent_turn rather than passing it - # via --argjson (which puts the full 21KB blob on the jq command line and - # blows up Cygwin/Windows argv limits with E2BIG / "Argument list too long"). - local tools_file; tools_file=$(mktemp) - printf '%s' "$TOOLS_JSON" > "$tools_file" + # Write the large blobs to files ONCE per agent_turn rather than passing + # them via --arg / --argjson. Combined budget (TOOLS_JSON ~21KB + system + # prompt ~25KB) easily exceeds Cygwin's ~32KB argv cap → E2BIG. + local tools_file system_file + tools_file=$(mktemp); system_file=$(mktemp) + printf '%s' "$TOOLS_JSON" > "$tools_file" + printf '%s' "$system_prompt" > "$system_file" while true; do local payload_file; payload_file=$(mktemp) jq -n \ --arg model "$LARRY_MODEL" \ --argjson max_tokens "$LARRY_MAX_TOKENS" \ - --arg system "$system_prompt" \ + --rawfile system "$system_file" \ --slurpfile messages "$MESSAGES_FILE" \ --slurpfile tools "$tools_file" \ '{model:$model, max_tokens:$max_tokens, system:$system, messages:$messages[0], tools:$tools[0]}' \ @@ -1059,12 +1075,12 @@ agent_turn() { local resp; resp=$(call_api "$payload_file") rm -f "$payload_file" - if [ -z "$resp" ]; then err "empty response from API (timeout or network?)"; rm -f "$tools_file"; return 1; fi + if [ -z "$resp" ]; then err "empty response from API (timeout or network?)"; rm -f "$tools_file" "$system_file"; return 1; fi local err_type; err_type=$(printf '%s' "$resp" | jq -r '.error.type // empty' 2>/dev/null) if [ -n "$err_type" ]; then err "API error: $err_type — $(printf '%s' "$resp" | jq -r '.error.message // "no message"')" - rm -f "$tools_file" + rm -f "$tools_file" "$system_file" return 1 fi @@ -1100,14 +1116,20 @@ agent_turn() { local result; result=$(execute_tool "$name" "$input_json") log_append '```'; log_append "$result"; log_append '```' + # Tool results can be large (read_file up to 250KB, ssh_exec up to + # 500 lines, etc.) — pass via tempfile, not --arg, to avoid Cygwin + # argv overflow. + local result_file; result_file=$(mktemp) + printf '%s' "$result" > "$result_file" results=$(printf '%s' "$results" | jq \ - --arg id "$tu_id" --arg c "$result" \ + --arg id "$tu_id" --rawfile c "$result_file" \ '. + [{"type":"tool_result","tool_use_id":$id,"content":$c}]') + rm -f "$result_file" done < <(printf '%s' "$resp" | jq -c '.content[] | select(.type=="tool_use")') add_user_tool_results "$results" done - rm -f "$tools_file" + rm -f "$tools_file" "$system_file" } # ─────────────────────────────────────────────────────────────────────────────