Repro
ctrlrelay run secops --repo <repo> from CLI manually.
- Pipeline returns BLOCKED with a question; row lands in
pending_resumes.
- Operator answers (via SQL or Telegram bridge).
- Poller's
_run_pending_resume_sweeper fires resume_secops_from_pending.
- Resume calls
worktree.ensure_bare_repo(repo) → git fetch --prune origin refs/heads/*:refs/heads/*.
- Fetch fails because dependabot has rebased an unrelated open PR's branch:
Resume error: git failed: From https://github.com/SemClone/ccda-dashboard
- [deleted] (none) -> dependabot/pip/werkzeug-3.1.8
! [rejected] dependabot/pip/requests-2.33.1 -> dependabot/pip/requests-2.33.1 (non-fast-forward)
b14bda1..3216dec main -> main
- Resume aborts; session sits at
failed. Operator's answer is never delivered to the agent.
Root cause
ensure_bare_repo (src/ctrlrelay/core/worktree.py:734-779) deliberately uses non-force refspec refs/heads/*:refs/heads/* (no leading +). Per the docstring this is intentional — it preserves unpushed local-ahead commits for the dev pipeline's create_worktree_with_new_branch reuse path (added in 210404d / #106).
The trade-off is that git fetch exits non-zero whenever any branch is non-fast-forward. _run_git raises on non-zero, so a single rebased dependabot branch on an unrelated PR is fatal — even though:
- The default branch updated cleanly (
b14bda1..3216dec main -> main succeeded).
- The resume only does
git worktree add <bare> main (worktree.py:141-146), so it doesn't care about any dependabot branch.
In short: a dev-pipeline safeguard is killing secops resumes on repos that get a steady stream of dependabot activity (i.e., every active repo).
Suggested fixes (in order of preference)
- Targeted fetch in the resume path. Have
resume_secops_from_pending (and any caller that just needs a fresh default branch) do a narrow git fetch origin <default_branch>:<default_branch> — non-force, but scoped so unrelated rebases don't matter.
- Pipeline-aware refspec. secops can tolerate force updates (it never pushes to dependabot branches). Plumb a
force=True flag through ensure_bare_repo and have secops use +refs/heads/*:refs/heads/*. Dev keeps the current safe semantics.
- Tolerate per-branch rejection in
ensure_bare_repo. Parse the fetch stderr, downgrade to a warning if every rejected ref is one we don't care about (i.e., not the default branch), but escalate if the default branch itself rejects. Riskier — silent staleness if logic drifts.
(1) is the smallest blast radius and mirrors how the bare-clone fix in #106 narrowed scope. Happy to send a PR if you want.
Related
Workaround
Rerun the pipeline fresh against current main:
ctrlrelay run secops --repo SemClone/ccda-dashboard
A fresh session creates a new worktree from current default-branch HEAD, so the stale-ref issue doesn't recur — but the operator's earlier BLOCKED answer is lost and the agent has to redo the analysis from scratch.
Repro
ctrlrelay run secops --repo <repo>from CLI manually.pending_resumes._run_pending_resume_sweeperfiresresume_secops_from_pending.worktree.ensure_bare_repo(repo)→git fetch --prune origin refs/heads/*:refs/heads/*.failed. Operator's answer is never delivered to the agent.Root cause
ensure_bare_repo(src/ctrlrelay/core/worktree.py:734-779) deliberately uses non-force refspecrefs/heads/*:refs/heads/*(no leading+). Per the docstring this is intentional — it preserves unpushed local-ahead commits for the dev pipeline'screate_worktree_with_new_branchreuse path (added in 210404d / #106).The trade-off is that
git fetchexits non-zero whenever any branch is non-fast-forward._run_gitraises on non-zero, so a single rebased dependabot branch on an unrelated PR is fatal — even though:b14bda1..3216dec main -> mainsucceeded).git worktree add <bare> main(worktree.py:141-146), so it doesn't care about any dependabot branch.In short: a dev-pipeline safeguard is killing secops resumes on repos that get a steady stream of dependabot activity (i.e., every active repo).
Suggested fixes (in order of preference)
resume_secops_from_pending(and any caller that just needs a fresh default branch) do a narrowgit fetch origin <default_branch>:<default_branch>— non-force, but scoped so unrelated rebases don't matter.force=Trueflag throughensure_bare_repoand have secops use+refs/heads/*:refs/heads/*. Dev keeps the current safe semantics.ensure_bare_repo. Parse the fetch stderr, downgrade to a warning if every rejected ref is one we don't care about (i.e., not the default branch), but escalate if the default branch itself rejects. Riskier — silent staleness if logic drifts.(1) is the smallest blast radius and mirrors how the bare-clone fix in #106 narrowed scope. Happy to send a PR if you want.
Related
Workaround
Rerun the pipeline fresh against current main:
A fresh session creates a new worktree from current default-branch HEAD, so the stale-ref issue doesn't recur — but the operator's earlier BLOCKED answer is lost and the agent has to redo the analysis from scratch.