From 7ecf372e408f71ccccab9accadeba23caec7aa03 Mon Sep 17 00:00:00 2001 From: openhands Date: Sun, 22 Mar 2026 23:58:50 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20feat:=20gardener=20defers=20all=20repo?= =?UTF-8?q?=20actions=20to=20a=20manifest=20=E2=80=94=20review=20gate=20co?= =?UTF-8?q?vers=20grooming=20decisions,=20not=20just=20docs=20(#572)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 (1M context) --- formulas/run-gardener.toml | 138 +++++++++++++---------------- gardener/AGENTS.md | 11 ++- gardener/gardener-run.sh | 174 +++++++++++++++++++++++++++++++++++-- 3 files changed, 237 insertions(+), 86 deletions(-) diff --git a/formulas/run-gardener.toml b/formulas/run-gardener.toml index 48d2f04..13d80bd 100644 --- a/formulas/run-gardener.toml +++ b/formulas/run-gardener.toml @@ -37,6 +37,9 @@ Set up the working environment for this gardener run. 3. Record the current HEAD SHA for AGENTS.md watermarks: HEAD_SHA=$(git rev-parse HEAD) echo "$HEAD_SHA" > /tmp/gardener-head-sha + +4. Initialize the pending-actions manifest (JSONL, converted to JSON at commit time): + printf '' > "$PROJECT_REPO_ROOT/gardener/pending-actions.jsonl" """ # ───────────────────────────────────────────────────────────────────── @@ -108,16 +111,10 @@ Sibling dependency rule (CRITICAL): Read AGENTS.md and extract the AD table. For each backlog issue, compare the issue title and body against each AD. If an issue clearly violates an AD: - a. Post a comment explaining the violation: - curl -sf -X POST -H "Authorization: token $CODEBERG_TOKEN" \ - -H "Content-Type: application/json" \ - "$CODEBERG_API/issues//comments" \ - -d '{"body":"Closing: violates AD-NNN (). See AGENTS.md § Architecture Decisions."}' - b. Close the issue: - curl -sf -X PATCH -H "Authorization: token $CODEBERG_TOKEN" \ - -H "Content-Type: application/json" \ - "$CODEBERG_API/issues/" \ - -d '{"state":"closed"}' + a. Write a comment action to the manifest: + echo '{"action":"comment","issue":NNN,"body":"Closing: violates AD-NNN (). See AGENTS.md § Architecture Decisions."}' >> "$PROJECT_REPO_ROOT/gardener/pending-actions.jsonl" + b. Write a close action to the manifest: + echo '{"action":"close","issue":NNN,"reason":"violates AD-NNN"}' >> "$PROJECT_REPO_ROOT/gardener/pending-actions.jsonl" c. Log to the result file: echo "ACTION: closed #NNN — violates AD-NNN" >> "$RESULT_FILE" @@ -134,20 +131,13 @@ Sibling dependency rule (CRITICAL): "## Affected files" section with at least one file path If either section is missing: - a. Look up the 'backlog' label ID: - BACKLOG_LABEL_ID=$(curl -sf -H "Authorization: token $CODEBERG_TOKEN" \ - "$CODEBERG_API/labels" | jq -r '.[] | select(.name == "backlog") | .id') - b. Post a comment listing what's missing: - curl -sf -X POST -H "Authorization: token $CODEBERG_TOKEN" \ - -H "Content-Type: application/json" \ - "$CODEBERG_API/issues//comments" \ - -d '{"body":"This issue is missing required sections. Please use the issue templates at `.codeberg/ISSUE_TEMPLATE/` — needs: ."}' + a. Write a comment action to the manifest: + echo '{"action":"comment","issue":NNN,"body":"This issue is missing required sections. Please use the issue templates at `.codeberg/ISSUE_TEMPLATE/` — needs: ."}' >> "$PROJECT_REPO_ROOT/gardener/pending-actions.jsonl" Where is a comma-separated list of what's absent (e.g. "acceptance criteria, affected files" or just "affected files"). - c. Remove the 'backlog' label: - curl -sf -X DELETE -H "Authorization: token $CODEBERG_TOKEN" \ - "$CODEBERG_API/issues//labels/$BACKLOG_LABEL_ID" - d. Log to the result file: + b. Write a remove_label action to the manifest: + echo '{"action":"remove_label","issue":NNN,"label":"backlog"}' >> "$PROJECT_REPO_ROOT/gardener/pending-actions.jsonl" + c. Log to the result file: echo "ACTION: stripped backlog from #NNN — missing: " >> "$RESULT_FILE" Well-structured issues (both sections present) are left untouched — @@ -203,16 +193,11 @@ session, so changes there would be lost. jq -r '[.group, (.issue | tostring)] | join("\\t")' "$DUST_FILE" | sort -u | cut -f1 | sort | uniq -c | sort -rn b. For each group with count >= 3: - Collect issue details and distinct issue numbers for the group - - Look up the backlog label ID: - BACKLOG_LABEL_ID=$(curl -sf -H "Authorization: token $CODEBERG_TOKEN" \ - "$CODEBERG_API/labels" | jq -r '.[] | select(.name == "backlog") | .id') - - Create a bundled backlog issue: - curl -sf -X POST -H "Authorization: token $CODEBERG_TOKEN" \ - -H "Content-Type: application/json" "$CODEBERG_API/issues" \ - -d '{"title":"fix: bundled dust cleanup — GROUP","body":"...","labels":[LABEL_ID]}' - - Close each source issue with a cross-reference comment: - curl ... "$CODEBERG_API/issues/NNN/comments" -d '{"body":"Bundled into #NEW"}' - curl ... "$CODEBERG_API/issues/NNN" -d '{"state":"closed"}' + - Write a create_issue action to the manifest: + echo '{"action":"create_issue","title":"fix: bundled dust cleanup — GROUP","body":"...","labels":["backlog"]}' >> "$PROJECT_REPO_ROOT/gardener/pending-actions.jsonl" + - Write comment + close actions for each source issue: + echo '{"action":"comment","issue":NNN,"body":"Bundled into dust cleanup issue for GROUP"}' >> "$PROJECT_REPO_ROOT/gardener/pending-actions.jsonl" + echo '{"action":"close","issue":NNN,"reason":"bundled into dust cleanup for GROUP"}' >> "$PROJECT_REPO_ROOT/gardener/pending-actions.jsonl" - Remove bundled items from dust.jsonl: jq -c --arg g "GROUP" 'select(.group != $g)' "$DUST_FILE" > "${DUST_FILE}.tmp" && mv "${DUST_FILE}.tmp" "$DUST_FILE" @@ -233,57 +218,42 @@ description = """ Review all issues labeled 'blocked' and decide their fate. (See issue #352 for the blocked label convention.) -1. Look up the 'blocked' label ID (Gitea needs integer IDs for label removal): - BLOCKED_LABEL_ID=$(curl -sf -H "Authorization: token $CODEBERG_TOKEN" \ - "$CODEBERG_API/labels" | jq -r '.[] | select(.name == "blocked") | .id') - If the lookup fails, skip label removal and just post comments. - -2. Fetch all blocked issues: +1. Fetch all blocked issues: curl -sf -H "Authorization: token $CODEBERG_TOKEN" \ "$CODEBERG_API/issues?state=open&type=issues&labels=blocked&limit=50" -3. For each blocked issue, read the full body and comments: +2. For each blocked issue, read the full body and comments: curl -sf -H "Authorization: token $CODEBERG_TOKEN" \ "$CODEBERG_API/issues/" curl -sf -H "Authorization: token $CODEBERG_TOKEN" \ "$CODEBERG_API/issues//comments" -4. Check dependencies — extract issue numbers from ## Dependencies / +3. Check dependencies — extract issue numbers from ## Dependencies / ## Depends on / ## Blocked by sections. For each dependency: curl -sf -H "Authorization: token $CODEBERG_TOKEN" \ "$CODEBERG_API/issues/" Check if the dependency is now closed. -5. For each blocked issue, choose ONE action: +4. For each blocked issue, choose ONE action: UNBLOCK — all dependencies are now closed or the blocking condition resolved: - a. Remove the 'blocked' label (using ID from step 1): - curl -sf -X DELETE -H "Authorization: token $CODEBERG_TOKEN" \ - "$CODEBERG_API/issues//labels/$BLOCKED_LABEL_ID" - b. Add context comment explaining what changed: - curl -sf -X POST -H "Authorization: token $CODEBERG_TOKEN" \ - -H "Content-Type: application/json" \ - "$CODEBERG_API/issues//comments" \ - -d '{"body":"Unblocked: "}' + a. Write a remove_label action to the manifest: + echo '{"action":"remove_label","issue":NNN,"label":"blocked"}' >> "$PROJECT_REPO_ROOT/gardener/pending-actions.jsonl" + b. Write a comment action to the manifest: + echo '{"action":"comment","issue":NNN,"body":"Unblocked: "}' >> "$PROJECT_REPO_ROOT/gardener/pending-actions.jsonl" NEEDS HUMAN — blocking condition is ambiguous, requires architectural decision, or involves external factors: - a. Post a diagnostic comment explaining what you found and what - decision is needed + a. Write a comment action to the manifest: + echo '{"action":"comment","issue":NNN,"body":""}' >> "$PROJECT_REPO_ROOT/gardener/pending-actions.jsonl" b. Leave the 'blocked' label in place CLOSE — issue is stale (blocked 30+ days with no progress on blocker), the blocker is wontfix, or the issue is no longer relevant: - a. Post a comment explaining why: - curl -sf -X POST -H "Authorization: token $CODEBERG_TOKEN" \ - -H "Content-Type: application/json" \ - "$CODEBERG_API/issues//comments" \ - -d '{"body":"Closing: "}' - b. Close the issue: - curl -sf -X PATCH -H "Authorization: token $CODEBERG_TOKEN" \ - -H "Content-Type: application/json" \ - "$CODEBERG_API/issues/" \ - -d '{"state":"closed"}' + a. Write a comment action to the manifest: + echo '{"action":"comment","issue":NNN,"body":"Closing: "}' >> "$PROJECT_REPO_ROOT/gardener/pending-actions.jsonl" + b. Write a close action to the manifest: + echo '{"action":"close","issue":NNN,"reason":""}' >> "$PROJECT_REPO_ROOT/gardener/pending-actions.jsonl" CRITICAL: If this step fails, log the failure and move on. """ @@ -421,49 +391,63 @@ needs = ["blocked-review"] id = "commit-and-pr" title = "One commit with all file changes, push, create PR, monitor to merge" description = """ -Collect all file changes from this run (AGENTS.md updates) into a single commit. -API calls (issue creation, PR comments, closures) already happened during the -run — only file changes need the PR. +Collect all file changes from this run (AGENTS.md updates + pending-actions +manifest) into a single commit. All repo mutation API calls (comments, closures, +label changes, issue creation) are deferred to the manifest — the orchestrator +executes them after the PR merges. -1. Check for staged or unstaged changes: +1. Convert the JSONL manifest to a JSON array: cd "$PROJECT_REPO_ROOT" + JSONL_FILE="$PROJECT_REPO_ROOT/gardener/pending-actions.jsonl" + JSON_FILE="$PROJECT_REPO_ROOT/gardener/pending-actions.json" + if [ -s "$JSONL_FILE" ]; then + jq -s '.' "$JSONL_FILE" > "$JSON_FILE" + else + echo '[]' > "$JSON_FILE" + fi + rm -f "$JSONL_FILE" + +2. Check for staged or unstaged changes: git status --porcelain - If there are no file changes, skip to step 3 — no commit, no PR needed. + If there are no file changes (no AGENTS.md updates AND manifest is empty []), + skip to step 4 — no commit, no PR needed. -2. If there are changes: +3. If there are changes: a. Create a branch: BRANCH="chore/gardener-$(date -u +%Y%m%d-%H%M)" git checkout -B "$BRANCH" b. Stage all modified AGENTS.md files: find . -name "AGENTS.md" -not -path "./.git/*" -exec git add {} + - c. Also stage any other files the gardener modified (if any): + c. Stage the pending-actions manifest: + git add gardener/pending-actions.json + d. Also stage any other files the gardener modified (if any): git add -u - d. Commit: + e. Commit: git commit -m "chore: gardener housekeeping $(date -u +%Y-%m-%d)" - e. Push: + f. Push: git push -u origin "$BRANCH" - f. Create a PR: + g. Create a PR: PR_RESPONSE=$(curl -sf -X POST \ -H "Authorization: token $CODEBERG_TOKEN" \ -H "Content-Type: application/json" \ "$CODEBERG_API/pulls" \ -d '{"title":"chore: gardener housekeeping", "head":"'"$BRANCH"'","base":"'"$PRIMARY_BRANCH"'", - "body":"Automated gardener housekeeping — AGENTS.md updates.\\n\\nReview-agent fast-tracks doc-only PRs."}') + "body":"Automated gardener housekeeping — AGENTS.md updates + pending actions manifest.\\n\\nReview `gardener/pending-actions.json` for proposed grooming actions (label changes, closures, comments). These execute after merge."}') PR_NUMBER=$(echo "$PR_RESPONSE" | jq -r '.number') - g. Save PR number for orchestrator tracking: + h. Save PR number for orchestrator tracking: echo "$PR_NUMBER" > /tmp/gardener-pr-${PROJECT_NAME}.txt - h. Signal the orchestrator to monitor CI: + i. Signal the orchestrator to monitor CI: echo "PHASE:awaiting_ci" > "$PHASE_FILE" - i. STOP and WAIT. Do NOT return to the primary branch. + j. STOP and WAIT. Do NOT return to the primary branch. The orchestrator polls CI, injects results and review feedback. When you receive injected CI or review feedback, follow its instructions, then write PHASE:awaiting_ci and wait again. -3. If no file changes existed (step 1 found nothing): +4. If no file changes existed (step 2 found nothing): echo "PHASE:done" > "$PHASE_FILE" -4. If PR creation fails, log the error and write PHASE:failed. +5. If PR creation fails, log the error and write PHASE:failed. """ needs = ["agents-update"] diff --git a/gardener/AGENTS.md b/gardener/AGENTS.md index 44fa402..684ccc3 100644 --- a/gardener/AGENTS.md +++ b/gardener/AGENTS.md @@ -17,8 +17,11 @@ runs directly from cron like the planner, predictor, and supervisor. - `gardener/gardener-run.sh` — Cron wrapper + orchestrator: lock, memory guard, consumes escalation replies, sources disinto project config, creates tmux session, injects formula prompt, monitors phase file, handles crash recovery via - `run_formula_and_monitor` + `run_formula_and_monitor`, executes pending-actions manifest after PR merge - `formulas/run-gardener.toml` — Execution spec: preflight, grooming, dust-bundling, blocked-review, agents-update, commit-and-pr +- `gardener/pending-actions.json` — Manifest of deferred repo actions (label changes, + closures, comments, issue creation). Written during grooming steps, committed to the + PR, reviewed alongside AGENTS.md changes, executed by gardener-run.sh after merge. **Environment variables consumed**: - `CODEBERG_TOKEN`, `CODEBERG_REPO`, `CODEBERG_API`, `PROJECT_NAME`, `PROJECT_REPO_ROOT` @@ -27,5 +30,7 @@ runs directly from cron like the planner, predictor, and supervisor. **Lifecycle**: gardener-run.sh (cron 0,6,12,18) → lock + memory guard → consume escalation replies → load formula + context → create tmux session → -Claude grooms backlog, bundles dust, reviews blocked issues, updates AGENTS.md, -commits and creates PR → `PHASE:done`. +Claude grooms backlog (writes proposed actions to manifest), bundles dust, +reviews blocked issues, updates AGENTS.md, commits manifest + docs to PR → +review-agent reviews all proposed actions → after merge, gardener-run.sh +executes manifest actions via API → `PHASE:done`. diff --git a/gardener/gardener-run.sh b/gardener/gardener-run.sh index 838f2c0..85e05f5 100755 --- a/gardener/gardener-run.sh +++ b/gardener/gardener-run.sh @@ -66,13 +66,24 @@ build_context_block AGENTS.md SCRATCH_CONTEXT=$(read_scratch_context "$SCRATCH_FILE") SCRATCH_INSTRUCTION=$(build_scratch_instruction "$SCRATCH_FILE") -# ── Build prompt (gardener needs extra API endpoints for issue management) ─ +# ── Build prompt (manifest format reference for deferred actions) ───────── GARDENER_API_EXTRA=" - Relabel: curl -sf -H \"Authorization: token \$CODEBERG_TOKEN\" -X PUT -H 'Content-Type: application/json' '${CODEBERG_API}/issues/{number}/labels' -d '{\"labels\":[LABEL_ID]}' - Comment: curl -sf -H \"Authorization: token \$CODEBERG_TOKEN\" -X POST -H 'Content-Type: application/json' '${CODEBERG_API}/issues/{number}/comments' -d '{\"body\":\"...\"}' - Close: curl -sf -H \"Authorization: token \$CODEBERG_TOKEN\" -X PATCH -H 'Content-Type: application/json' '${CODEBERG_API}/issues/{number}' -d '{\"state\":\"closed\"}' - Edit body: curl -sf -H \"Authorization: token \$CODEBERG_TOKEN\" -X PATCH -H 'Content-Type: application/json' '${CODEBERG_API}/issues/{number}' -d '{\"body\":\"new body\"}' -" + +## Pending-actions manifest (REQUIRED) +All repo mutations (comments, closures, label changes, issue creation) MUST be +written to the JSONL manifest instead of calling APIs directly. Append one JSON +object per line to: \$PROJECT_REPO_ROOT/gardener/pending-actions.jsonl + +Supported actions: + {\"action\":\"add_label\", \"issue\":NNN, \"label\":\"priority\"} + {\"action\":\"remove_label\", \"issue\":NNN, \"label\":\"backlog\"} + {\"action\":\"close\", \"issue\":NNN, \"reason\":\"already implemented\"} + {\"action\":\"comment\", \"issue\":NNN, \"body\":\"Relates to issue 1031\"} + {\"action\":\"create_issue\", \"title\":\"...\", \"body\":\"...\", \"labels\":[\"backlog\"]} + {\"action\":\"edit_body\", \"issue\":NNN, \"body\":\"new body\"} + +The commit-and-pr step converts JSONL to JSON array. The orchestrator executes +actions after the PR merges. Do NOT call mutation APIs directly during the run." build_prompt_footer "$GARDENER_API_EXTRA" # Extend phase protocol with merge-through instructions for compaction survival @@ -121,6 +132,154 @@ ${PROMPT_FOOTER}" # Handles CI polling, review injection, merge, and cleanup after PR creation. # Lighter than dev/phase-handler.sh — tailored for gardener doc-only PRs. +# ── Post-merge manifest execution ───────────────────────────────────── +# Reads gardener/pending-actions.json and executes each action via API. +# Failed actions are logged but do not block completion. +# shellcheck disable=SC2317 # called indirectly via _gardener_merge +_gardener_execute_manifest() { + local manifest_file="$PROJECT_REPO_ROOT/gardener/pending-actions.json" + if [ ! -f "$manifest_file" ]; then + log "manifest: no pending-actions.json — skipping" + return 0 + fi + + local count + count=$(jq 'length' "$manifest_file" 2>/dev/null || echo 0) + if [ "$count" -eq 0 ]; then + log "manifest: empty — skipping" + return 0 + fi + + log "manifest: executing ${count} actions" + + local i=0 + while [ "$i" -lt "$count" ]; do + local action issue + action=$(jq -r ".[$i].action" "$manifest_file") + issue=$(jq -r ".[$i].issue // empty" "$manifest_file") + + case "$action" in + add_label) + local label label_id + label=$(jq -r ".[$i].label" "$manifest_file") + label_id=$(curl -sf -H "Authorization: token ${CODEBERG_TOKEN}" \ + "${CODEBERG_API}/labels" | jq -r --arg n "$label" \ + '.[] | select(.name == $n) | .id') || true + if [ -n "$label_id" ]; then + if curl -sf -X POST -H "Authorization: token ${CODEBERG_TOKEN}" \ + -H 'Content-Type: application/json' \ + "${CODEBERG_API}/issues/${issue}/labels" \ + -d "{\"labels\":[${label_id}]}" >/dev/null 2>&1; then + log "manifest: add_label '${label}' to #${issue}" + else + log "manifest: FAILED add_label '${label}' to #${issue}" + fi + else + log "manifest: FAILED add_label — label '${label}' not found" + fi + ;; + + remove_label) + local label label_id + label=$(jq -r ".[$i].label" "$manifest_file") + label_id=$(curl -sf -H "Authorization: token ${CODEBERG_TOKEN}" \ + "${CODEBERG_API}/labels" | jq -r --arg n "$label" \ + '.[] | select(.name == $n) | .id') || true + if [ -n "$label_id" ]; then + if curl -sf -X DELETE -H "Authorization: token ${CODEBERG_TOKEN}" \ + "${CODEBERG_API}/issues/${issue}/labels/${label_id}" >/dev/null 2>&1; then + log "manifest: remove_label '${label}' from #${issue}" + else + log "manifest: FAILED remove_label '${label}' from #${issue}" + fi + else + log "manifest: FAILED remove_label — label '${label}' not found" + fi + ;; + + close) + local reason + reason=$(jq -r ".[$i].reason // empty" "$manifest_file") + if curl -sf -X PATCH -H "Authorization: token ${CODEBERG_TOKEN}" \ + -H 'Content-Type: application/json' \ + "${CODEBERG_API}/issues/${issue}" \ + -d '{"state":"closed"}' >/dev/null 2>&1; then + log "manifest: closed #${issue} (${reason})" + else + log "manifest: FAILED close #${issue}" + fi + ;; + + comment) + local body escaped_body + body=$(jq -r ".[$i].body" "$manifest_file") + escaped_body=$(printf '%s' "$body" | jq -Rs '.') + if curl -sf -X POST -H "Authorization: token ${CODEBERG_TOKEN}" \ + -H 'Content-Type: application/json' \ + "${CODEBERG_API}/issues/${issue}/comments" \ + -d "{\"body\":${escaped_body}}" >/dev/null 2>&1; then + log "manifest: commented on #${issue}" + else + log "manifest: FAILED comment on #${issue}" + fi + ;; + + create_issue) + local title body labels escaped_title escaped_body label_ids + title=$(jq -r ".[$i].title" "$manifest_file") + body=$(jq -r ".[$i].body" "$manifest_file") + labels=$(jq -r ".[$i].labels // [] | .[]" "$manifest_file") + escaped_title=$(printf '%s' "$title" | jq -Rs '.') + escaped_body=$(printf '%s' "$body" | jq -Rs '.') + # Resolve label names to IDs + label_ids="[]" + if [ -n "$labels" ]; then + local all_labels ids_json="" + all_labels=$(curl -sf -H "Authorization: token ${CODEBERG_TOKEN}" \ + "${CODEBERG_API}/labels") || true + while IFS= read -r lname; do + local lid + lid=$(echo "$all_labels" | jq -r --arg n "$lname" \ + '.[] | select(.name == $n) | .id') || true + [ -n "$lid" ] && ids_json="${ids_json:+${ids_json},}${lid}" + done <<< "$labels" + [ -n "$ids_json" ] && label_ids="[${ids_json}]" + fi + if curl -sf -X POST -H "Authorization: token ${CODEBERG_TOKEN}" \ + -H 'Content-Type: application/json' \ + "${CODEBERG_API}/issues" \ + -d "{\"title\":${escaped_title},\"body\":${escaped_body},\"labels\":${label_ids}}" >/dev/null 2>&1; then + log "manifest: created issue '${title}'" + else + log "manifest: FAILED create_issue '${title}'" + fi + ;; + + edit_body) + local body escaped_body + body=$(jq -r ".[$i].body" "$manifest_file") + escaped_body=$(printf '%s' "$body" | jq -Rs '.') + if curl -sf -X PATCH -H "Authorization: token ${CODEBERG_TOKEN}" \ + -H 'Content-Type: application/json' \ + "${CODEBERG_API}/issues/${issue}" \ + -d "{\"body\":${escaped_body}}" >/dev/null 2>&1; then + log "manifest: edited body of #${issue}" + else + log "manifest: FAILED edit_body #${issue}" + fi + ;; + + *) + log "manifest: unknown action '${action}' — skipping" + ;; + esac + + i=$((i + 1)) + done + + log "manifest: execution complete (${count} actions processed)" +} + # shellcheck disable=SC2317 # called indirectly by monitor_phase_loop _gardener_merge() { local merge_response merge_http_code @@ -133,6 +292,7 @@ _gardener_merge() { if [ "$merge_http_code" = "200" ] || [ "$merge_http_code" = "204" ]; then log "gardener PR #${_GARDENER_PR} merged" + _gardener_execute_manifest printf 'PHASE:done\n' > "$PHASE_FILE" return 0 fi @@ -144,6 +304,7 @@ _gardener_merge() { "${CODEBERG_API}/pulls/${_GARDENER_PR}" | jq -r '.merged // false') || true if [ "$pr_merged" = "true" ]; then log "gardener PR #${_GARDENER_PR} already merged" + _gardener_execute_manifest printf 'PHASE:done\n' > "$PHASE_FILE" return 0 fi @@ -422,6 +583,7 @@ Then stop and wait." if [ "$pr_merged" = "true" ]; then log "gardener PR #${_GARDENER_PR} merged externally" + _gardener_execute_manifest printf 'PHASE:done\n' > "$PHASE_FILE" return 0 fi