fix(tools): write large JSON payloads to tmpfile to prevent truncation (closes #493)

When init commands include many file contents (state, roadmap, requirements,
etc.), the JSON output can exceed Claude Code's Bash tool buffer (~50KB),
causing parse errors. The output() function now auto-detects large payloads
and writes to a tmpfile, returning @file:/path instead. All workflows that
consume init output handle both formats.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Lex Christopherson
2026-02-15 11:35:39 -06:00
parent bc13b4919f
commit 8d977328e4
4 changed files with 37 additions and 4 deletions

View File

@@ -468,7 +468,16 @@ function output(result, raw, rawValue) {
if (raw && rawValue !== undefined) {
process.stdout.write(String(rawValue));
} else {
process.stdout.write(JSON.stringify(result, null, 2));
const json = JSON.stringify(result, null, 2);
// Large payloads exceed Claude Code's Bash tool buffer (~50KB).
// Write to tmpfile and output the path prefixed with @file: so callers can detect it.
if (json.length > 50000) {
const tmpPath = path.join(require('os').tmpdir(), `gsd-${Date.now()}.json`);
fs.writeFileSync(tmpPath, json, 'utf-8');
process.stdout.write('@file:' + tmpPath);
} else {
process.stdout.write(json);
}
}
process.exit(0);
}

View File

@@ -15,7 +15,15 @@ Read config.json for planning behavior settings.
Load execution context (uses `init execute-phase` for full context, including file contents):
```bash
INIT=$(node ~/.claude/get-shit-done/bin/gsd-tools.cjs init execute-phase "${PHASE}" --include state,config)
INIT_RAW=$(node ~/.claude/get-shit-done/bin/gsd-tools.cjs init execute-phase "${PHASE}" --include state,config)
# Large payloads are written to a tmpfile — output starts with @file:/path
if [[ "$INIT_RAW" == @file:* ]]; then
INIT_FILE="${INIT_RAW#@file:}"
INIT=$(cat "$INIT_FILE")
rm -f "$INIT_FILE"
else
INIT="$INIT_RAW"
fi
```
Extract from init JSON: `executor_model`, `commit_docs`, `phase_dir`, `phase_number`, `plans`, `summaries`, `incomplete_plans`.

View File

@@ -15,7 +15,15 @@ Read all files referenced by the invoking prompt's execution_context before star
Load all context in one call (include file contents to avoid redundant reads):
```bash
INIT=$(node ~/.claude/get-shit-done/bin/gsd-tools.cjs init plan-phase "$PHASE" --include state,roadmap,requirements,context,research,verification,uat)
INIT_RAW=$(node ~/.claude/get-shit-done/bin/gsd-tools.cjs init plan-phase "$PHASE" --include state,roadmap,requirements,context,research,verification,uat)
# Large payloads are written to a tmpfile — output starts with @file:/path
if [[ "$INIT_RAW" == @file:* ]]; then
INIT_FILE="${INIT_RAW#@file:}"
INIT=$(cat "$INIT_FILE")
rm -f "$INIT_FILE"
else
INIT="$INIT_RAW"
fi
```
Parse JSON for: `researcher_model`, `planner_model`, `checker_model`, `research_enabled`, `plan_checker_enabled`, `commit_docs`, `phase_found`, `phase_dir`, `phase_number`, `phase_name`, `phase_slug`, `padded_phase`, `has_research`, `has_context`, `has_plans`, `plan_count`, `planning_exists`, `roadmap_exists`.

View File

@@ -12,7 +12,15 @@ Read all files referenced by the invoking prompt's execution_context before star
**Load progress context (with file contents to avoid redundant reads):**
```bash
INIT=$(node ~/.claude/get-shit-done/bin/gsd-tools.cjs init progress --include state,roadmap,project,config)
INIT_RAW=$(node ~/.claude/get-shit-done/bin/gsd-tools.cjs init progress --include state,roadmap,project,config)
# Large payloads are written to a tmpfile — output starts with @file:/path
if [[ "$INIT_RAW" == @file:* ]]; then
INIT_FILE="${INIT_RAW#@file:}"
INIT=$(cat "$INIT_FILE")
rm -f "$INIT_FILE"
else
INIT="$INIT_RAW"
fi
```
Extract from init JSON: `project_exists`, `roadmap_exists`, `state_exists`, `phases`, `current_phase`, `next_phase`, `milestone_version`, `completed_count`, `phase_count`, `paused_at`.