fleet-collect.sh 2.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768
  1. #!/usr/bin/env bash
  2. # fleet-collect.sh - gate one fleet-worker JSON result; print its text, set exit code.
  3. #
  4. # Reads a `claude -p --output-format json` result object (file arg or stdin),
  5. # prints the worker's final assistant text to stdout, and exits 0 only when the
  6. # worker truly succeeded. Encodes the spec footgun: the `subtype` field reads
  7. # "success" even on an API error - the real gate is is_error==false (corroborated
  8. # by the process exit code and api_error_status). Use this to decide which fanned-
  9. # out worker branches are worth landing.
  10. #
  11. # Usage: fleet-collect.sh [--quiet] [RESULT_JSON]
  12. # fleet-worker --output-format json "task" | fleet-collect.sh
  13. # Input: result JSON as a file arg, or on stdin
  14. # Output: stdout = the worker's final text (.result), only on success
  15. # Stderr: one human status line (OK / FAILED + api_error_status)
  16. # Exit: 0 success; 10 worker failed (is_error / api_error); 3 file not found;
  17. # 4 malformed / not a result object; 2 usage; 5 missing jq
  18. #
  19. # Examples:
  20. # fleet-collect.sh task-a.result.json && echo "branch fleet/task-a is landable"
  21. # fleet-worker --output-format json "fix the failing test" | fleet-collect.sh -q
  22. set -uo pipefail
  23. EXIT_OK=0; EXIT_USAGE=2; EXIT_NOT_FOUND=3; EXIT_VALIDATION=4; EXIT_MISSING_DEP=5; EXIT_FAIL=10
  24. QUIET=0; SRC=""
  25. while [ $# -gt 0 ]; do
  26. case "$1" in
  27. -h|--help) awk 'NR==1{next} /^#/{sub(/^# ?/,""); print; next} {exit}' "$0"; exit "$EXIT_OK" ;;
  28. -q|--quiet) QUIET=1 ;;
  29. -*) echo "fleet-collect.sh: unknown flag: $1 (try --help)" >&2; exit "$EXIT_USAGE" ;;
  30. *) if [ -z "$SRC" ]; then SRC="$1"; else echo "fleet-collect.sh: too many arguments" >&2; exit "$EXIT_USAGE"; fi ;;
  31. esac
  32. shift
  33. done
  34. command -v jq >/dev/null 2>&1 || { echo "fleet-collect.sh: jq is required" >&2; exit "$EXIT_MISSING_DEP"; }
  35. emit() { [ "$QUIET" -eq 1 ] || printf '%s\n' "$1" >&2; }
  36. if [ -n "$SRC" ]; then
  37. [ -f "$SRC" ] || { echo "fleet-collect.sh: file not found: $SRC" >&2; exit "$EXIT_NOT_FOUND"; }
  38. DATA="$(cat "$SRC")"
  39. else
  40. DATA="$(cat)"
  41. fi
  42. printf '%s' "$DATA" | jq -e . >/dev/null 2>&1 || {
  43. echo "fleet-collect.sh: input is not valid JSON" >&2; exit "$EXIT_VALIDATION"; }
  44. # Note: `.is_error // empty` is WRONG - jq's `//` treats boolean false like null,
  45. # so a genuine is_error:false would read as empty. Gate on has()/tostring instead.
  46. is_error="$(printf '%s' "$DATA" | jq -r 'if has("is_error") then (.is_error|tostring) else "" end')"
  47. api_status="$(printf '%s' "$DATA" | jq -r '.api_error_status // empty')"
  48. result="$(printf '%s' "$DATA" | jq -r '.result // ""')"
  49. if [ -z "$is_error" ]; then
  50. echo "fleet-collect.sh: not a result object (no .is_error field)" >&2
  51. exit "$EXIT_VALIDATION"
  52. fi
  53. if [ "$is_error" = "false" ]; then
  54. printf '%s\n' "$result"
  55. emit "OK"
  56. exit "$EXIT_OK"
  57. fi
  58. emit "FAILED (is_error=$is_error api_error_status=${api_status:-none})"
  59. exit "$EXIT_FAIL"