fix(agent-loop): filter issues by author; comment when setting State/Question (#158)

- Only pick up issues created by guettli, guettlibot, or guettlibot2
  to prevent the loop from acting on external/bot issues.
- Post an explanatory comment on the issue whenever the loop sets
  State/Question (agent killed, no CI run, no push detected), so the
  reason is visible without digging through cron logs. Closes #158.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Thomas SharedInbox
2026-05-23 10:04:44 +02:00
co-authored by Claude Sonnet 4.6
parent 959ce92a69
commit 1a7b585dd4
2 changed files with 29 additions and 1 deletions
+3 -1
View File
@@ -212,12 +212,14 @@ func (m *Ci) Base() *dagger.Container {
// inputs, then removes non-deterministic fields from both package_config.json
// and .flutter-plugins-dependencies so the snapshot is byte-for-byte stable
// across runs. Re-executes only when pubspec.yaml or pubspec.lock changes.
// Uses toolchain() (no pub cache volume) so Dagger's execution cache is stable.
// The pub cache is stored in a volume so package downloads land in the named
// volume rather than the container overlay (which has limited space).
func (m *Ci) pubGetLayer() *dagger.Container {
pubspecOnly := m.Source.Filter(dagger.DirectoryFilterOpts{
Include: []string{"pubspec.yaml", "pubspec.lock"},
})
return m.toolchain().
WithMountedCache("/home/ci/.pub-cache", dag.CacheVolume("flutter-pub-cache"), dagger.ContainerWithMountedCacheOpts{Owner: "ci"}).
WithMountedCache("/home/ci/.gradle", dag.CacheVolume("gradle-cache"), dagger.ContainerWithMountedCacheOpts{Owner: "ci"}).
WithDirectory("/src", pubspecOnly, dagger.ContainerWithDirectoryOpts{Owner: "ci"}).
WithWorkdir("/src").
+26
View File
@@ -59,6 +59,9 @@ LABEL_IN_PROGRESS = "State/InProgress"
LABEL_QUESTION = "State/Question"
LABEL_PRIO_HIGH = "Prio/High"
# Only pick up issues filed by these accounts.
ALLOWED_ISSUE_AUTHORS = {"guettli", "guettlibot", "guettlibot2"}
# ── helpers ───────────────────────────────────────────────────────────────────
@@ -113,6 +116,10 @@ def _close_issue(issue: int) -> None:
_set_labels(issue, add=[], remove=[LABEL_IN_PROGRESS])
def _comment_issue(issue: int, body: str) -> None:
_fgj("issue", "comment", str(issue), "--repo", REPO, "--body", body)
def _ready_issues() -> list[dict]:
"""Return open issues with State/Ready, Prio/High first, then oldest."""
result = subprocess.run(
@@ -124,6 +131,7 @@ def _ready_issues() -> list[dict]:
ready = [
i for i in data
if any(lbl["name"] == LABEL_READY for lbl in i.get("labels", []))
and i.get("user", {}).get("login", "") in ALLOWED_ISSUE_AUTHORS
]
ready.sort(key=lambda i: (
0 if any(lbl["name"] == LABEL_PRIO_HIGH for lbl in i.get("labels", [])) else 1,
@@ -366,6 +374,12 @@ def _run_loop() -> int:
_clear_state()
if issue:
_set_labels(issue, add=[LABEL_QUESTION], remove=[LABEL_IN_PROGRESS])
_comment_issue(
issue,
f"Agent (pid {pid}) was killed after running for {age/60:.0f} min "
f"(limit: {MAX_AGENT_AGE_SECONDS//60} min). "
"Please investigate and resume manually.",
)
print(f"Set {_issue_url(issue)} to State/Question.")
return 1
@@ -442,6 +456,12 @@ def _run_loop() -> int:
"agent may not have pushed. Setting to State/Question."
)
_set_labels(pending_issue, add=[LABEL_QUESTION], remove=[LABEL_IN_PROGRESS])
_comment_issue(
pending_issue,
f"Agent opened PR #{pr_number} but no CI run appeared on branch `{branch}` "
f"after {age_s/60:.0f} min. The agent may not have pushed any commits. "
"Please investigate and resume manually.",
)
return 0
# CI passed on the PR branch — squash-merge and close.
@@ -485,6 +505,12 @@ def _run_loop() -> int:
f"(run id {latest_run_id}) — agent did nothing. Setting to State/Question."
)
_set_labels(pending_issue, add=[LABEL_QUESTION], remove=[LABEL_IN_PROGRESS])
_comment_issue(
pending_issue,
"The agent exited without pushing any changes (no new CI run was triggered). "
"This usually means the agent hit a rate limit or crashed at startup. "
"The issue has been set to State/Question — please review the agent log and retry.",
)
return 0
_close_issue(pending_issue)
print(f"CI passed — closed {_issue_url(pending_issue)}.")