Skip to content

Commit cd28ca1

Browse files
Fix Overmind changes cleanup script: correct UUID encoding, resilient loop
- list-changes --format json renders UUID as a hyphenated string (via ToMap()), not the base64 the raw DeleteChange call needs since it's a proto bytes field - convert it back before calling delete. This is why every delete call was failing against the real API. - Read the delete loop from a process substitution instead of piping into it, and check curl's HTTP status explicitly rather than using -f, so one failed delete doesn't abort the rest or produce a confusing "jq: broken pipe" error - failures are now collected and reported at the end. - Skip the token exchange entirely in --dry-run, since it never calls delete and doesn't need a delete-capable key. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 85b106a commit cd28ca1

1 file changed

Lines changed: 54 additions & 21 deletions

File tree

.github/scripts/overmind-cleanup-changes.sh

Lines changed: 54 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,9 @@ set -euo pipefail
2424
# creating a fresh pair of demo PRs so only this run's changes remain
2525
# afterwards. --keep N keeps the N most-recently-created matches instead.
2626
#
27-
# Requires: the overmind CLI, jq, curl
27+
# Requires: the overmind CLI, jq, curl, python3
2828
# Requires OVM_API_KEY with the "changes:write" scope (the same scope
29-
# already used by start-change/end-change/submit-plan). Not yet tested
30-
# end-to-end since no OVM_API_KEY is available in the environment this was
31-
# written in - run with --dry-run first.
29+
# already used by start-change/end-change/submit-plan).
3230

3331
APP="https://app.overmind.tech"
3432
TITLE_PATTERN="narrow internal ingress"
@@ -59,7 +57,10 @@ MATCHES="$(jq -s --arg pattern "$TITLE_PATTERN" '
5957

6058
MATCH_COUNT="$(jq 'length' <<<"$MATCHES")"
6159
echo "Found $MATCH_COUNT change(s) matching /$TITLE_PATTERN/i:"
62-
jq -r '.[] | " - \(.metadata.createdAt) \(.properties.title) (uuid b64: \(.metadata.UUID // "?"))"' <<<"$MATCHES"
60+
# NB: .metadata.UUID here is the CLI's human-readable hyphenated UUID string
61+
# (list-changes renders it via the SDK's ToMap(), not raw proto JSON) - it is
62+
# NOT the base64 the raw DeleteChange call below needs. See uuid_to_base64().
63+
jq -r '.[] | " - \(.metadata.createdAt) \(.properties.title) (uuid: \(.metadata.UUID // "?"))"' <<<"$MATCHES"
6364

6465
TO_DELETE="$(jq -c --argjson keep "$KEEP" '.[$keep:]' <<<"$MATCHES")"
6566
DELETE_COUNT="$(jq 'length' <<<"$TO_DELETE")"
@@ -71,30 +72,62 @@ fi
7172

7273
echo "Deleting $DELETE_COUNT change(s), keeping $KEEP most-recent match(es)..."
7374

74-
# The CLI never prints the token it authenticates with internally, so this
75-
# is the one bit of auth we have to redo ourselves for the delete call.
76-
API_URL="$(curl -sf "$APP/api/public/instance-data" | jq -r '.api_url')"
77-
TOKEN="$(curl -sf -X POST "$API_URL/apikeys.ApiKeyService/ExchangeKeyForToken" \
78-
-H "Content-Type: application/json" -H "Connect-Protocol-Version: 1" \
79-
-d "{\"apiKey\": \"$OVM_API_KEY\"}" | jq -r '.accessToken')"
75+
if [[ "$DRY_RUN" != "true" ]]; then
76+
# The CLI never prints the token it authenticates with internally, so this
77+
# is the one bit of auth we have to redo ourselves for the delete call.
78+
# Skipped entirely in --dry-run so a dry run never needs a delete-capable
79+
# key, just something that can list.
80+
API_URL="$(curl -sf "$APP/api/public/instance-data" | jq -r '.api_url')"
81+
TOKEN="$(curl -sf -X POST "$API_URL/apikeys.ApiKeyService/ExchangeKeyForToken" \
82+
-H "Content-Type: application/json" -H "Connect-Protocol-Version: 1" \
83+
-d "{\"apiKey\": \"$OVM_API_KEY\"}" | jq -r '.accessToken')"
8084

81-
if [[ -z "$TOKEN" || "$TOKEN" == "null" ]]; then
82-
echo "Failed to exchange OVM_API_KEY for a token" >&2
83-
exit 1
85+
if [[ -z "$TOKEN" || "$TOKEN" == "null" ]]; then
86+
echo "Failed to exchange OVM_API_KEY for a token" >&2
87+
exit 1
88+
fi
8489
fi
8590

86-
echo "$TO_DELETE" | jq -c '.[]' | while read -r change; do
87-
UUID_B64="$(jq -r '.metadata.UUID' <<<"$change")"
91+
# DeleteChangeRequest.UUID is a proto `bytes` field, so the raw Connect+JSON
92+
# call needs it base64-encoded - convert the CLI's hyphenated string back to
93+
# the raw 16 bytes first.
94+
uuid_to_base64() {
95+
python3 -c "import sys, uuid, base64; print(base64.b64encode(uuid.UUID(sys.argv[1]).bytes).decode())" "$1"
96+
}
97+
98+
FAILURES=0
99+
RESPONSE_FILE="$(mktemp)"
100+
trap 'rm -f "$RESPONSE_FILE"' EXIT
101+
102+
# Read from a process substitution rather than piping into the while loop:
103+
# piping meant a failure in the loop body raced against jq still writing
104+
# more output, which surfaced as a confusing "jq: broken pipe" error on top
105+
# of whatever actually failed.
106+
while IFS= read -r change; do
107+
UUID_STR="$(jq -r '.metadata.UUID' <<<"$change")"
88108
TITLE="$(jq -r '.properties.title' <<<"$change")"
89109

90110
if [[ "$DRY_RUN" == "true" ]]; then
91-
echo " [dry-run] would delete: $TITLE"
111+
echo " [dry-run] would delete: $TITLE ($UUID_STR)"
92112
continue
93113
fi
94114

95-
curl -sf -X POST "$API_URL/changes.ChangesService/DeleteChange" \
115+
UUID_B64="$(uuid_to_base64 "$UUID_STR")"
116+
117+
HTTP_STATUS="$(curl -s -o "$RESPONSE_FILE" -w '%{http_code}' -X POST "$API_URL/changes.ChangesService/DeleteChange" \
96118
-H "Content-Type: application/json" -H "Connect-Protocol-Version: 1" \
97119
-H "Authorization: Bearer $TOKEN" \
98-
-d "{\"UUID\": \"$UUID_B64\"}" >/dev/null
99-
echo " deleted: $TITLE"
100-
done
120+
-d "{\"UUID\": \"$UUID_B64\"}")"
121+
122+
if [[ "$HTTP_STATUS" == "200" ]]; then
123+
echo " deleted: $TITLE ($UUID_STR)"
124+
else
125+
echo " FAILED to delete: $TITLE ($UUID_STR) - HTTP $HTTP_STATUS: $(cat "$RESPONSE_FILE")" >&2
126+
FAILURES=$((FAILURES + 1))
127+
fi
128+
done < <(jq -c '.[]' <<<"$TO_DELETE")
129+
130+
if [[ "$FAILURES" -gt 0 ]]; then
131+
echo "$FAILURES deletion(s) failed" >&2
132+
exit 1
133+
fi

0 commit comments

Comments
 (0)