From 82135afd059f9c2ad4a8cb69e26ecee886a301d4 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 16 May 2026 16:51:41 +0000 Subject: [PATCH] Fly deploy: stop double-escaping secret values The previous "Pull runtime secrets" step wrote KEY=VALUE entries with printf '%q' (shell-quoted), then the "Stage Fly secrets" step read them back and passed them to fly secrets set without re-evaluating the quotes. The escape characters survived all the way to Fly's storage, so INBOUND_PHONE_VOICE_MAP ended up as the literal string {"+13143..."} (with backslashes), which the JSON parser rejected at boot with "Invalid JSON: Unexpected token '\\'". Fix: switch to NUL-separated KEY=VALUE pairs in a temp file. The write side uses printf '%s=%s\0' and the read side uses read -d '' to consume the NUL delimiter. Quote the array expansion to keep each pair as one argv entry to flyctl. Result: values arrive at Fly literally, no escapes. This also removes the shellcheck SC2068 disable comment because the quoting is now correct. --- .github/workflows/fly-deploy.yml | 33 ++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/.github/workflows/fly-deploy.yml b/.github/workflows/fly-deploy.yml index 7f9e0ee..29a31a8 100644 --- a/.github/workflows/fly-deploy.yml +++ b/.github/workflows/fly-deploy.yml @@ -89,7 +89,8 @@ jobs: "ai/anthropic/api_key:ANTHROPIC_API_KEY" "voice/inbound_phone_map:INBOUND_PHONE_VOICE_MAP" ) - : > "$RUNNER_TEMP/secrets.env" + : > "$RUNNER_TEMP/secrets.bin" + count=0 for pair in "${mapping[@]}"; do suffix="${pair%%:*}" name="${pair##*:}" @@ -99,13 +100,17 @@ jobs: --query 'Parameter.Value' --output text) # Mask so the value never appears in subsequent log output. echo "::add-mask::$value" - # Single-quoted so JSON values pass through intact. - printf "%s=%q\n" "$name" "$value" >> "$RUNNER_TEMP/secrets.env" + # NUL-separated KEY=VALUE entries — preserves values + # literally (no shell escaping, no \n issues). The next + # step reads back with `read -d ''`. + printf '%s=%s\0' "$name" "$value" >> "$RUNNER_TEMP/secrets.bin" + count=$((count + 1)) done - # Add the two static settings: - printf "%s=%q\n" "SERVER_DOMAIN" "$FLY_APP_NAME.fly.dev" >> "$RUNNER_TEMP/secrets.env" - printf "%s=%q\n" "VALIDATE_TWILIO_SIGNATURE" "true" >> "$RUNNER_TEMP/secrets.env" - echo "wrote $(wc -l < $RUNNER_TEMP/secrets.env) entries" + # Static settings: + printf '%s=%s\0' "SERVER_DOMAIN" "$FLY_APP_NAME.fly.dev" >> "$RUNNER_TEMP/secrets.bin" + printf '%s=%s\0' "VALIDATE_TWILIO_SIGNATURE" "true" >> "$RUNNER_TEMP/secrets.bin" + count=$((count + 2)) + echo "wrote $count entries" - name: Setup flyctl # superfly/flyctl-actions/setup-flyctl@master left flyctl off @@ -166,12 +171,16 @@ jobs: # --stage defers the implicit re-deploy until our explicit # deploy step. Otherwise fly secrets set triggers a second # rollout and we waste a build. + # + # Read NUL-separated KEY=VALUE pairs the previous step wrote. + # NUL separation + IFS= + -r preserves every byte (no shell + # escaping). Quote the array expansion so each pair stays a + # single argv entry to flyctl. args=() - while IFS= read -r line; do - args+=("$line") - done < "$RUNNER_TEMP/secrets.env" - # shellcheck disable=SC2068 - fly secrets set ${args[@]} --stage -a "$FLY_APP_NAME" + while IFS= read -r -d '' pair; do + args+=("$pair") + done < "$RUNNER_TEMP/secrets.bin" + fly secrets set "${args[@]}" --stage -a "$FLY_APP_NAME" - name: Deploy env: