Skip to content

feat: PR Tracker — watch GitHub PR activity per channel#202

Merged
LIU9293 merged 3 commits into
mainfrom
feat/pr-tracker-1776743582
Apr 21, 2026
Merged

feat: PR Tracker — watch GitHub PR activity per channel#202
LIU9293 merged 3 commits into
mainfrom
feat/pr-tracker-1776743582

Conversation

@LIU9293

@LIU9293 LIU9293 commented Apr 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a PR Tracker feature next to Tasks / Cron in Settings.

  • Discovers GitHub repos automatically from each channel's Working Directory (parsed from git remote get-url origin).
  • Each tracker is a (source channel, github repo) row stored in ~/.config/ode/inbox.db and is disabled by default.
  • When enabled, a polling scheduler queries GitHub for new PR activity (issue comments, review comments, reviews, PR updates) since the last cursor, aggregates events per PR, and dispatches one agent run per PR with new events.
  • The agent's final response is posted to a configured target channel (no thread). Per-event and aggregate dedupe rows are written so re-polls don't re-fire.
  • Token resolution chain: per-tracker token → global default → gh auth token fallback.
  • Trackers whose source repo disappears at scan time are flagged missing (greyed out in UI) and can be deleted by the user.

Architecture (mirrors Tasks / Cron)

Layer File
Git remote parsing packages/utils/git-remote.ts
SQLite tables + CRUD packages/config/local/pr-trackers.ts (pr_trackers, pr_tracker_events, pr_tracker_settings)
GitHub REST client packages/core/pr-tracker/github.ts
Polling scheduler packages/core/pr-tracker/scheduler.ts
HTTP API packages/core/web/routes/pr-trackers.ts
CLI packages/core/cli-handlers/pr-tracker.ts (ode pr-tracker ...)
Web UI packages/web-ui/src/routes/(settings)/pr-tracker/+page.svelte
Wire start/stop packages/core/index.ts

CLI

ode pr-tracker scan
ode pr-tracker list [--enabled|--disabled|--missing] [--json]
ode pr-tracker show <id> [--json]
ode pr-tracker enable <id> --target-channel <channelId>
ode pr-tracker disable <id>
ode pr-tracker update <id> [--agent ...] [--prompt-file ...] [--interval <s>] [--token ...] [--target-channel ...]
ode pr-tracker run <id>
ode pr-tracker delete <id>
ode pr-tracker events <id> [--limit N] [--json]
ode pr-tracker settings [--show | --set-interval <s> | --set-agent <provider> | --set-prompt-file <path> | --set-token <token>]

Defaults

  • Poll interval: 30 minutes (configurable globally and per-tracker; min 60s)
  • Agent: opencode (configurable)
  • Prompt template (overridable per-tracker):
    A pull request in {{repo_full_name}} has new activity:
    PR #{{pr_number}}: {{pr_title}} by {{pr_author}}
    URL: {{pr_url}}
    
    New events since last check:
    {{new_events_summary}}
    
    Please review these updates, decide if action is needed
    (reply, assign, flag), and take the next step.
    
  • Event summary truncates to the latest 20 events with a "...and N more" marker so prompts stay bounded.
  • A per-poll cap of 5 PRs avoids agent fan-out storms when a quiet repo suddenly gets busy.
  • First-enable sets the cursor to now — no historical backfill.

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.

Kai Liu added 2 commits April 21, 2026 13:22
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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread packages/core/pr-tracker/scheduler.ts Outdated
Comment thread packages/core/pr-tracker/github.ts Outdated
Comment thread packages/utils/git-remote.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread packages/config/local/pr-trackers.ts Outdated
Comment thread packages/core/pr-tracker/github.ts Outdated
Comment thread packages/core/pr-tracker/scheduler.ts
…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).
@LIU9293
LIU9293 merged commit fbddf24 into main Apr 21, 2026
2 checks passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +271 to +274
return githubGet<GhIssueComment[]>(
ctx,
`/repos/${owner}/${repo}/issues/comments?since=${encodeURIComponent(sinceIso)}&per_page=100&sort=updated&direction=asc`,
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Comment on lines +298 to +300
const pageSize = 50;
const maxPages = 4;
const collected: GhPullRequest[] = [];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Comment on lines +216 to +219
triggerPrTrackerNow(id)
.then((outcome) => {
log.info("Manually triggered PR tracker poll", outcome);
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant