feat: PR Tracker — watch GitHub PR activity per channel#202
Conversation
Introduces a new Settings section that discovers GitHub repos from every configured channel's workingDirectory and, when enabled, periodically polls GitHub for new PR activity, aggregates events per PR, and dispatches an agent run that posts results to a configured target channel. The feature mirrors the Task / Cron architecture: SQLite tables in the existing inbox.db, a polling scheduler, HTTP API, `ode pr-tracker` CLI, and a Web UI page. Trackers are disabled by default; enabling one requires picking a target channel. Event dedupe (tracker, kind, github_event_id) prevents double-dispatch across polls, and a per-PR aggregation keeps agent runs bounded even when many events arrive at once. Token resolution falls back from per-tracker → global default → local `gh auth token`.
…43582 # Conflicts: # packages/utils/index.ts
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4619875c3d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1b1936c681
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…andling) Closes the six issues flagged in the PR #202 review: P1 (data loss): - markPrTrackerPolled failure branch no longer advances last_polled_at; the cursor is also used as the GitHub `since` window, so pushing it on failure permanently skipped unprocessed activity. New setPrTrackerCursor helper lets the scheduler move the cursor explicitly. - pollTracker no longer advances the cursor to "now" when the per-poll PR cap is hit. Instead it processes PRs oldest-updated-first, then rewinds the cursor to just before the oldest skipped event so the next tick picks them up. - fetchPrActivity uses Promise.allSettled and throws GitHubActivityFetchError when every top-level endpoint fails, so a total outage surfaces as a poll failure (cursor stays pinned) instead of looking like "no activity". P2 (correctness / noise): - /issues/comments results are now filtered by pull_request_url, so comments on regular issues no longer fabricate bogus PR buckets. - Scheduler threads tracker.repoHost into fetchPrActivity via a new resolveApiBaseUrl helper (github.com -> api.github.com, GHES hosts -> https://{host}/api/v3). - parseGitHubRemote now whitelists hosts (github.com and github.*) and rejects lookalikes like notgithub.com. Existing tests updated for the new behaviors; added 9 regression tests covering the fixes (cursor semantics, host rejection, enterprise base URL routing, total-failure propagation, plain-issue comment filtering).
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6f14818fe1
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| return githubGet<GhIssueComment[]>( | ||
| ctx, | ||
| `/repos/${owner}/${repo}/issues/comments?since=${encodeURIComponent(sinceIso)}&per_page=100&sort=updated&direction=asc`, | ||
| ); |
There was a problem hiding this comment.
Paginate GitHub comment queries before advancing cursor
fetchIssueComments (and the parallel fetchReviewComments) only requests one page with per_page=100 and never follows pagination, so busy intervals can drop comment events beyond the first page. The scheduler still treats that as a successful poll and advances last_polled_at, which permanently skips the omitted comment activity unless the same PR is updated again later. Please iterate comment pages (or keep the cursor pinned when truncation occurs) to avoid silent data loss.
Useful? React with 👍 / 👎.
| const pageSize = 50; | ||
| const maxPages = 4; | ||
| const collected: GhPullRequest[] = []; |
There was a problem hiding this comment.
Remove fixed 200-PR fetch cap to prevent skipped updates
The pull fetcher stops after maxPages = 4 (200 PRs), so if more PRs than that are updated since the cursor, later pages are never examined. pollTracker can only rewind the cursor relative to fetched PRs, so omitted PRs that are older than that rewind point fall permanently behind since and will never dispatch. This breaks activity tracking on high-volume repos; continue paging until reaching sinceMs (or explicitly preserve cursor before any omitted page).
Useful? React with 👍 / 👎.
| triggerPrTrackerNow(id) | ||
| .then((outcome) => { | ||
| log.info("Manually triggered PR tracker poll", outcome); | ||
| }) |
There was a problem hiding this comment.
Return run API errors when tracker poll does not start
The manual run route fire-and-forgets triggerPrTrackerNow(id) and always returns success, but triggerPrTrackerNow/pollTracker report conditions like tracker not found, tracker disabled, and already running via outcome.error instead of throwing. In those cases the CLI/UI still reports the run as triggered even though no poll happened. Await the outcome and map non-empty errors to appropriate HTTP statuses so callers can react correctly.
Useful? React with 👍 / 👎.
Summary
Adds a PR Tracker feature next to Tasks / Cron in Settings.
git remote get-url origin).~/.config/ode/inbox.dband is disabled by default.gh auth tokenfallback.missing(greyed out in UI) and can be deleted by the user.Architecture (mirrors Tasks / Cron)
packages/utils/git-remote.tspackages/config/local/pr-trackers.ts(pr_trackers,pr_tracker_events,pr_tracker_settings)packages/core/pr-tracker/github.tspackages/core/pr-tracker/scheduler.tspackages/core/web/routes/pr-trackers.tspackages/core/cli-handlers/pr-tracker.ts(ode pr-tracker ...)packages/web-ui/src/routes/(settings)/pr-tracker/+page.sveltepackages/core/index.tsCLI
Defaults
opencode(configurable)Tests
packages/utils/git-remote.test.ts— 12 tests covering URL shape parsing.packages/config/local/pr-trackers.test.ts— 19 tests covering scan, enable/disable, dedupe, due selection, missing/reactivate.packages/core/pr-tracker/github.test.ts— 12 tests covering token resolution, prompt rendering, event aggregation, and degraded-mode behavior on partial endpoint failures.Full suite:
bun test→ 330 pass, 0 fail.