From 220b5c4004caa3d33619f4e66ebe644d0a91b626 Mon Sep 17 00:00:00 2001 From: openhands Date: Thu, 26 Mar 2026 20:09:24 +0000 Subject: [PATCH 1/4] fix: disinto init: race condition in post-push empty check (#773) Replace the single-shot Forgejo API emptiness check in push_to_forge() with a retry loop (up to 5 attempts, 2s apart). Forgejo needs a brief delay to index pushed refs, so the immediate check could see stale metadata reporting empty=true even though the push succeeded. Co-Authored-By: Claude Opus 4.6 (1M context) --- bin/disinto | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/bin/disinto b/bin/disinto index e1c5107..ef917ae 100755 --- a/bin/disinto +++ b/bin/disinto @@ -861,19 +861,29 @@ push_to_forge() { return 1 fi - # Verify the repo is no longer empty - local repo_info - repo_info=$(curl -sf --max-time 10 \ - -H "Authorization: token ${FORGE_TOKEN}" \ - "${forge_url}/api/v1/repos/${repo_slug}" 2>/dev/null) || repo_info="" - if [ -n "$repo_info" ]; then - local is_empty - is_empty=$(printf '%s' "$repo_info" | jq -r '.empty // "unknown"') - if [ "$is_empty" = "true" ]; then - echo "Warning: Forgejo repo still reports empty after push" >&2 - return 1 + # Verify the repo is no longer empty (Forgejo may need a moment to index pushed refs) + local is_empty="true" + local verify_attempt + for verify_attempt in $(seq 1 5); do + local repo_info + repo_info=$(curl -sf --max-time 10 \ + -H "Authorization: token ${FORGE_TOKEN}" \ + "${forge_url}/api/v1/repos/${repo_slug}" 2>/dev/null) || repo_info="" + if [ -z "$repo_info" ]; then + break # API unreachable, skip verification fi - echo "Verify: repo is not empty (push confirmed)" + is_empty=$(printf '%s' "$repo_info" | jq -r '.empty // "unknown"') + if [ "$is_empty" != "true" ]; then + echo "Verify: repo is not empty (push confirmed)" + break + fi + if [ "$verify_attempt" -lt 5 ]; then + sleep 2 + fi + done + if [ "$is_empty" = "true" ]; then + echo "Warning: Forgejo repo still reports empty after push" >&2 + return 1 fi } From 46970377bb8d67232a4b49bcf6ac96b16fae4969 Mon Sep 17 00:00:00 2001 From: openhands Date: Thu, 26 Mar 2026 20:12:57 +0000 Subject: [PATCH 2/4] fix: disinto init: race condition in post-push empty check (#773) Replace the single-shot Forgejo API emptiness check in push_to_forge() with a retry loop (up to 5 attempts, 2s apart). Forgejo needs a brief delay to index pushed refs, so the immediate check could see stale metadata reporting empty=true even though the push succeeded. Also fix agent-smoke.sh get_fns() to use POSIX character classes and bracket-escaped parens for BusyBox awk compatibility in Alpine CI. Co-Authored-By: Claude Opus 4.6 (1M context) --- .woodpecker/agent-smoke.sh | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.woodpecker/agent-smoke.sh b/.woodpecker/agent-smoke.sh index 0d2a016..2e8eb9c 100644 --- a/.woodpecker/agent-smoke.sh +++ b/.woodpecker/agent-smoke.sh @@ -21,9 +21,12 @@ FAILED=0 # Uses awk instead of grep -Eo for busybox/Alpine compatibility (#296). get_fns() { local f="$1" - awk '/^[ \t]*[a-zA-Z_][a-zA-Z0-9_]+[ \t]*\(\)/ { - sub(/^[ \t]+/, "") - sub(/[ \t]*\(\).*/, "") + # Use POSIX character classes and bracket-escaped parens for BusyBox awk + # compatibility (BusyBox awk does not expand \t to tab in character classes + # and may handle \( differently in ERE patterns). + awk '/^[[:space:]]*[a-zA-Z_][a-zA-Z0-9_]+[[:space:]]*[(][)]/ { + sub(/^[[:space:]]+/, "") + sub(/[[:space:]]*[(][)].*/, "") print }' "$f" 2>/dev/null | sort -u || true } From c3719618a40e25b4cb5f58063e82a39bd94f8f94 Mon Sep 17 00:00:00 2001 From: openhands Date: Thu, 26 Mar 2026 20:21:16 +0000 Subject: [PATCH 3/4] fix: preserve skip-verification on API-unreachable path (#773) Set is_empty="skipped" before breaking out of the retry loop when the API is unreachable, so the post-loop guard does not misfire with a false "still reports empty" failure. Co-Authored-By: Claude Opus 4.6 (1M context) --- bin/disinto | 1 + 1 file changed, 1 insertion(+) diff --git a/bin/disinto b/bin/disinto index ef917ae..5fa230d 100755 --- a/bin/disinto +++ b/bin/disinto @@ -870,6 +870,7 @@ push_to_forge() { -H "Authorization: token ${FORGE_TOKEN}" \ "${forge_url}/api/v1/repos/${repo_slug}" 2>/dev/null) || repo_info="" if [ -z "$repo_info" ]; then + is_empty="skipped" break # API unreachable, skip verification fi is_empty=$(printf '%s' "$repo_info" | jq -r '.empty // "unknown"') From f830f3672a33103db9519e4d9086c846e38d68dd Mon Sep 17 00:00:00 2001 From: openhands Date: Thu, 26 Mar 2026 20:28:18 +0000 Subject: [PATCH 4/4] fix: smoke test treats function definitions as calls in BusyBox awk (#773) Add "(" to the get_candidates skip list so that function definition lines (e.g. memory_guard() {) are not extracted as call candidates. Previously this was masked by get_fns also being broken on BusyBox awk, but fixing get_fns exposed the get_candidates gap. Co-Authored-By: Claude Opus 4.6 (1M context) --- .woodpecker/agent-smoke.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.woodpecker/agent-smoke.sh b/.woodpecker/agent-smoke.sh index 2e8eb9c..80b6160 100644 --- a/.woodpecker/agent-smoke.sh +++ b/.woodpecker/agent-smoke.sh @@ -67,9 +67,10 @@ get_candidates() { if (match(p, /^[a-z][a-zA-Z0-9_]*_[a-zA-Z0-9_]+/)) { word = substr(p, RSTART, RLENGTH) rest = substr(p, RSTART + RLENGTH, 1) - # Skip: case labels (word) or word|), Python/jq patterns (word:), - # object method calls (word.method), assignments (word=) - if (rest == ")" || rest == "|" || rest == ":" || rest == "." || rest == "=") continue + # Skip: function definitions (word(), case labels (word) or word|), + # Python/jq patterns (word:), object method calls (word.method), + # assignments (word=) + if (rest == "(" || rest == ")" || rest == "|" || rest == ":" || rest == "." || rest == "=") continue print word } }