From 52a994d9332f9f4c8abf3c16ee6492af3cb051d0 Mon Sep 17 00:00:00 2001 From: openhands Date: Thu, 28 May 2026 18:25:20 -0600 Subject: [PATCH 1/2] fix(github-repo-monitor): silence 409 in fire_callback; document PATCH re-enable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The automation service marks a run COMPLETED on process exit. The explicit fire_callback("COMPLETED") call at script end then races against that and receives HTTP 409 Conflict, producing a noisy "Callback error (non-fatal)" line in every run log. Fix: catch HTTPError 409 specifically and return without printing — it means the run is already done, which is the desired state. Also add two troubleshooting rows to SKILL.md: - The 409 symptom, its root cause, and the fix. - The PATCH-resets-enabled behaviour: after PATCHing an automation the service sets enabled=false; a follow-up PATCH {"enabled":true} is required to reactivate it. Co-authored-by: openhands --- skills/github-repo-monitor/SKILL.md | 2 ++ skills/github-repo-monitor/scripts/main.py | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/skills/github-repo-monitor/SKILL.md b/skills/github-repo-monitor/SKILL.md index 7de2e8f7..5572cdf3 100644 --- a/skills/github-repo-monitor/SKILL.md +++ b/skills/github-repo-monitor/SKILL.md @@ -303,3 +303,5 @@ Each cron run executes `main.py`, which: | Same comment processed twice | `processed_comment_ids` cleared | State file was deleted; harmless but duplicate comment may appear | | Summary never posted | Conversation stuck in `running` | Open the conversation in the OpenHands UI; agent may need input | | No events detected after first run | `last_poll` in the future | Delete the state file to reset; it will be recreated on next run | +| `Callback error (non-fatal): HTTP Error 409: Conflict` in run logs | Automation service auto-marks the run complete on process exit; explicit `fire_callback("COMPLETED")` at script end then conflicts | Fixed in `scripts/main.py` ≥ this version — the 409 is now caught and ignored silently | +| Automation stops firing after updating it via PATCH | PATCH resets `enabled` to `false` | Follow every PATCH with `{"enabled": true}` to re-activate | diff --git a/skills/github-repo-monitor/scripts/main.py b/skills/github-repo-monitor/scripts/main.py index c0548e86..40a1b00a 100644 --- a/skills/github-repo-monitor/scripts/main.py +++ b/skills/github-repo-monitor/scripts/main.py @@ -98,6 +98,10 @@ def fire_callback(status: str = "COMPLETED", error: str | None = None) -> None: ) try: urllib.request.urlopen(req) + except urllib.error.HTTPError as exc: + if exc.code == 409: + return # Run already marked complete by the automation service — ignore. + print(f"Callback error (non-fatal): {exc}") except Exception as exc: print(f"Callback error (non-fatal): {exc}") From 604752e35e62bd017d0e8f1cc5e573316f442f6d Mon Sep 17 00:00:00 2001 From: openhands Date: Fri, 29 May 2026 06:30:52 -0600 Subject: [PATCH 2/2] fix(github-repo-monitor): replace vague 'local mode' note with RUNTIME_URL check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous 'Local mode only' callout and the AGENTS.md guidance both used 'local/sandboxed environment' as the criterion for choosing cron polling over event triggers. This is imprecise — in production K8s pods AGENT_SERVER_URL and HOST are absent, but RUNTIME_URL is set to a reachable external URL. Replace with a concrete, executable check: echo "RUNTIME_URL=${RUNTIME_URL}" - set and not localhost/127.0.0.1 → environment can receive GitHub webhooks → prefer event-triggered automation - unset, empty, or localhost → use cron polling (this skill) Changes: - Update the intro callout to reference RUNTIME_URL explicitly. - Add Step 0 to the Setup Workflow so the agent checks RUNTIME_URL before proceeding and surfaces the event-trigger recommendation to the user when appropriate. Co-authored-by: openhands --- skills/github-repo-monitor/SKILL.md | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/skills/github-repo-monitor/SKILL.md b/skills/github-repo-monitor/SKILL.md index 5572cdf3..6f63d5d4 100644 --- a/skills/github-repo-monitor/SKILL.md +++ b/skills/github-repo-monitor/SKILL.md @@ -29,8 +29,11 @@ On every subsequent run: - When a conversation goes idle/finished/error the agent's final response is posted back as a GitHub comment. -> **Local mode only.** This automation targets the local OpenHands setup -> (`dev:automation` stack). A cloud/webhook variant is out of scope here. +> **Cron polling vs. event triggers:** This skill uses cron polling and works in +> any deployment. If `RUNTIME_URL` is set and does not contain `localhost` or +> `127.0.0.1`, the environment can receive GitHub webhook events directly — prefer +> an event-triggered automation (see the `openhands-automation` skill) rather than +> polling. Use this skill only when `RUNTIME_URL` is unset, empty, or local. --- @@ -69,6 +72,25 @@ function without GitHub credentials. Follow these steps in order. +### Step 0 - Check deployment environment + +Check whether the environment can receive GitHub webhook events: + +```bash +echo "RUNTIME_URL=${RUNTIME_URL}" +``` + +- If `RUNTIME_URL` is **set and does not contain `localhost` or `127.0.0.1`** → + tell the user: + *"Your environment has a reachable `RUNTIME_URL` (`$RUNTIME_URL`), so GitHub can + deliver events directly. An event-triggered automation (e.g. `pull_request.opened` + or `issue_comment.created`) using the `openhands-automation` skill would be more + immediate and efficient than cron polling. Continue with this skill only if you + specifically want polling."* + If the user confirms they want polling anyway, continue to Step 1. +- If `RUNTIME_URL` is **unset, empty, or contains `localhost`/`127.0.0.1`** → + proceed directly to Step 1. + ### Step 1 - Verify GITHUB_TOKEN Fetch the secret and run the `curl` check above.