---
id: "0035"
date: 2026-07-13
status: Accepted
supersedes:
superseded-by:
affected-paths: [plugins/os-backlog/bin/wakeup-poll, plugins/os-backlog/lib/backlog/wakeup.rb]
affected-components: [os-backlog]
---

# 0035 — Issue-triggered project AI wakeup via polling, not webhooks

## Context

Issue #28 (design spike, blocked-by #27/ADR-0034): when an issue is filed with a project — often via cross-project file-don't-fix — that project's AI should be awakened to triage or act, instead of waiting for the human to open a session. The spike must pick a trigger mechanism, autonomy guardrails, credential scope, and context handoff. The human pre-chose the direction: polling trigger + tmux execution surface (ADR-0036 records the tmux convention).

## Decision

Trigger is a poller, not webhooks: a cron/heartbeat script (pilot: `plugins/os-backlog/bin/wakeup-poll`, one pass per invocation, no cron installed yet) scans opted-in projects for NEW open issues. (1) Scope: rows of the global project index `~/.cc-os/projects.json` (ADR-0034) whose tracker is `forgejo:` or `github:`, AND whose per-project `.cc-os/config` carries the explicit opt-in marker `wakeup=true` (the index row path locates the config; a later config-write slice may mirror the flag into the row — the config file remains the authority). Planka/repo-tracked projects are never polled. (2) New-ness: last-seen state persisted at `~/.cc-os/wakeup-state.json` (per-repo highest issue index seen); first scan of a repo baselines without waking anything. (3) Autonomy, in ADR-0029 vocabulary via issue labels: `afk-ready` -> spawn an acting session; `semi` -> spawn a triage-only session (label + comment, stop at proposing — never implement); anything else (hitl or unlabeled) -> notify-only. Notification policy v2 (pull-only) applies: the scan appends to a summary the human pulls (`~/.cc-os/wakeup-summary.md`) — no push notifications. (4) Credentials: the poller lists issues through tea's existing login only; it NEVER reads `~/.credentials` contents; spawned sessions get Planka creds the established way (source in a subshell). (5) Context handoff: the spawned session's initial prompt carries the issue number, title, URL, and the Discoverer block if present (ADR-0034), plus its autonomy tier. (6) Execution surface is a tmux session per ADR-0036.

## Consequences

Easier: host-agnostic (works for Forgejo and GitHub identically through tea/gh), no listener endpoint, no inbound network surface, trivially disabled (remove the cron line or the wakeup flag). Harder: latency is the poll interval, and the poller is per-machine state — two machines polling would double-wake (accepted for the single-operator pilot; state file is per-host). Non-goals, explicitly: no webhooks; no auto-merge/auto-close; no acting on hitl/unlabeled issues; wakened sessions still obey file-don't-fix (ADR-0034) — they never edit other projects; no bulk backfill of pre-existing open issues (baseline-then-watch).

## Alternatives rejected

1) Forgejo webhooks -> local listener — rejected for the first cut: needs an always-up endpoint reachable from the Forgejo host, doesn't cover GitHub without a second mechanism, and adds an inbound attack surface; polling via tea reuses existing credentials and works everywhere. Webhooks remain the natural upgrade if poll latency ever matters. 2) Opt-in as a field only in the global index row — rejected: the index is derived and rebuildable (ADR-0034); a rebuild would silently drop opt-ins. Authority lives in the per-project .cc-os/config (ADR-0027). 3) Push notifications per new issue (Apprise, ADR-0024) — rejected here: notification policy v2 is pull-only for ambient status; push stays reserved for the human explicitly asking.
