eleven / ocho Public

server: mesh_peer_boxes uses aws s3 ls, not the broken s3api subcommand (#840)

Found live on box A after applying #822's Terraform grant: aws s3api
list-objects-v2 (and the older list-objects) crash on this fleet's
aws-cli/Python combination with ValueError "badly formed help string"
— a Python 3.13+ argparse regression in botocore's generated help
text, thrown while building the argument parser, before any request
is even sent. mesh_peer_boxes silently swallowed this (any aws
failure degrades to "no peers found"), so the feature was inert
rather than broken-loud, but it never actually worked.

aws s3 ls hits the same ListObjectsV2 API underneath and is
unaffected — it's what the rest of this script already relies on for
uploads. Its plain-text output (date, time, size, key) has everything
mesh_peer_boxes needs; switched the staleness-cutoff comparison to
match its space-separated timestamp shape instead of ISO8601's
T-separated one.

Verified live against box A's real bucket/prefix and credentials
(mesh_peer_boxes sourced in isolation, not the whole script, to avoid
triggering a real publish): returns [] cleanly now instead of
crashing — the stats prefix is genuinely empty today since no box has
ever published there yet. Dry-run fixtures regenerate identical to
committed (this code path never runs in dry-run mode).
Co-authored-by: Claude <noreply@anthropic.com>
James Adam df2872f637273029f12466d27ab86c049eb58c3f parent 79bcf10
1 file changed, +27 −16
  • deploy/status-publish.sh +27 −16
diff --git a/deploy/status-publish.sh b/deploy/status-publish.sh
index a0afb74..1d7d78e 100755
--- a/deploy/status-publish.sh
+++ b/deploy/status-publish.sh
@@ -365,31 +365,42 @@ mesh_peer_boxes() {
region=$(env_str "$ROUTER_ENV" LCHAT_HIBERNATE_REGION)
region=${region:-eu-west-1}
- local listf="$TMP/mesh-stats-objects.json"
- if ! aws s3api list-objects-v2 --bucket "$bucket" --prefix "${prefix}-stats/" \
- --region "$region" --output json > "$listf" 2>/dev/null; then
- echo '[]'; return
- fi
-
- # ISO8601 timestamps sort lexicographically, so a plain string compare
- # against the cutoff is exact — no date-library parsing needed, and a box
+ # `aws s3 ls` (the high-level command), never `aws s3api list-objects*` —
+ # the latter crashes on this fleet's aws-cli/Python combination (a Python
+ # 3.13+ argparse regression in botocore's generated help text: ValueError
+ # "badly formed help string", thrown while building the argument parser,
+ # before any request is even sent — found live on box A, 2026-07-13,
+ # confirmed against both list-objects-v2 and the older list-objects; `aws
+ # s3 ls`/`aws s3 cp` are unaffected, and are what the rest of this script
+ # already relies on). `s3 ls` hits the same ListObjectsV2 API underneath;
+ # its plain-text output already has everything needed (date, time, key).
+ local listing
+ listing=$(aws s3 ls "s3://$bucket/${prefix}-stats/" --region "$region" 2>/dev/null) || true
+ [ -n "$listing" ] || { echo '[]'; return; }
+
+ # A plain string compare against the cutoff is exact as long as both sides
+ # use the same "YYYY-MM-DD HH:MM:SS" shape (space-, not T-separated —
+ # `aws s3 ls`'s own format) — no date-library parsing needed, and a box
# whose clock is off only ever misjudges its OWN staleness, same tradeoff
# mesh.go's own reader-vs-S3-clock note accepts for membership.
- local cutoff peers="[]" key lastmod tmp body
- cutoff=$(date -u -d "-${MESH_STATS_STALE_SECONDS} seconds" +%Y-%m-%dT%H:%M:%S)
-
- while IFS=$'\t' read -r key lastmod; do
+ local cutoff peers="[]" line date_part time_part key tmp body
+ cutoff=$(date -u -d "-${MESH_STATS_STALE_SECONDS} seconds" '+%Y-%m-%d %H:%M:%S')
+
+ while IFS= read -r line; do
+ [ -n "$line" ] || continue
+ date_part=$(printf '%s' "$line" | awk '{print $1}')
+ time_part=$(printf '%s' "$line" | awk '{print $2}')
+ key=$(printf '%s' "$line" | awk '{print $4}')
[ -n "$key" ] || continue
- [ "$lastmod" '>' "$cutoff" ] || continue # stale — treat like a dead mesh member
+ [ "$date_part $time_part" '>' "$cutoff" ] || continue # stale — treat like a dead mesh member
tmp="$TMP/mesh-stat-$(printf '%s' "$key" | tr '/' '_').json"
- aws s3 cp "s3://$bucket/$key" "$tmp" --region "$region" >/dev/null 2>&1 || continue
+ aws s3 cp "s3://$bucket/${prefix}-stats/$key" "$tmp" --region "$region" >/dev/null 2>&1 || continue
body=$(cat "$tmp" 2>/dev/null) || continue
[ -n "$body" ] || continue
peers=$(sqj "SELECT json_insert(
json('$(printf '%s' "$peers" | sed "s/'/''/g")'), '\$[#]',
json('$(printf '%s' "$body" | sed "s/'/''/g")'))")
- done < <(sqj "SELECT json_extract(value, '\$.Key') || char(9) || json_extract(value, '\$.LastModified')
- FROM json_each(json_extract(readfile('$listf'), '\$.Contents'))" 2>/dev/null)
+ done <<< "$listing"
printf '%s' "$peers"
}