Skip to content

Four correctness bugs found while mapping the GitHub sync #72

Description

@javiertoledo

What happened

Four independent correctness bugs found while mapping the GitHub sync for the pull-request epic. None was user-reported; all are visible in the code at 0f646ac. Filed together because each is small, but they are unrelated and can be picked up separately.


1. The issue backfill cursor uses our write time, not GitHub's update time

services/api/src/github/issues-sync.ts:111-116 takes max(gh_issues.synced_at) as the cursor, and passes it to GitHub as since (:126) — but since filters on the issue's updated_at, while synced_at is set to now() at write time (:171). The cursor is therefore always ahead of the data it is meant to track.

Two consequences:

  • A permanent blind window on every run. A sync at 12:00 mirrors an issue whose updated_at is 09:00 and moves the cursor to 12:00. Anything updated between 09:00 and 12:00 that fell outside the 10-page cap (:123) is never fetched again.
  • Unrelated events poison the cursor. bumpGhIssueCommentCount (:76) writes syncedAt: new Date() on an issue_comment webhook. One comment on one issue advances the backfill cursor past every other issue's pending updates.

The cursor should be max(gh_updated_at), with a small overlap window to absorb clock skew and second-granularity truncation. The upsert is idempotent, so the overlap costs nothing.


2. Slash-command runs render "triggered by: null"

services/api/src/github/router.ts:179 writes:

createdBy: { type: "github", login: sender },

Every other producer writes { type, id } — including processor.ts:551, which writes the correct { type: "github", id: … } for the same actor kind. triggeredByOf (services/api/src/routes/v1/github.ts:407-417) only reads createdBy.id, so a run triggered by /builder in a comment shows no author in the story timeline.

The forward fix is one line. It needs a companion, though: createdBy is jsonb and is not migrated, so every run created before the fix would keep rendering as unattributed. triggeredByOf should fall back to createdBy.login for the legacy shape.

services/api/src/watchtower/canary.ts:125 has the same shape of bug — { type: "system", name: "watchtower" }, also without id.

Note that proposalOpener (services/api/src/routes/v1/shared.ts:41-52) also reads createdBy.type/createdBy.id, but as a dual-control authorisation check that only ever matches user or key. It is correct as written and should not be touched.


3. Runs are joined to issues by number alone, so two repos in one project collide

linkedRunsForIssues (services/api/src/routes/v1/github.ts:651-677) keys runs by issue number:

inArray(sql`(${runs.gh}->>'issueNumber')::int`, numbers)

repos is per-project and a project may have several (repos_org_project_idx). Issue #4 in repo A and issue #4 in repo B therefore share a key, and each row shows the other's runs.

loadIssue (:628-649) has the matching problem on the read side — it filters org_id + project_id + number with .limit(1) and ignores repo_id, so /stories/4 in a two-repo project resolves arbitrarily.

Both are pre-existing and only bite multi-repo projects, but mirroring pull requests makes the number space denser (GitHub shares one counter between issues and PRs per repo), so it is worth fixing before that lands rather than after.


4. Two hairlines are missing from the pipeline board at tablet width

apps/web/components/project/pipeline.tsx:75 applies:

max-lg:odd:border-l-0 sm:max-lg:[&:nth-child(3n+1)]:border-l-0

At the smlg breakpoint the grid is three columns, so only children 1 and 4 begin a row and should lose their left seam. But max-lg:odd also matches children 3 and 5. The union strips the border from 1, 3, 4 and 5, and the board renders with two seams missing.

Giving every cell a left border plus -ml-px is correct at any column count and removes both selectors. (In CSS Grid a stretched item's used width is track − margins, so a −1px left margin widens the cell leftwards onto the seam without moving its right edge — no overflow.)


How to reproduce

  1. Sync a repository with more issues than one page, let a partial sync occur, then observe issues updated in the gap never re-appearing.
  2. Comment /builder on an issue from GitHub; open the resulting story timeline; the run shows no author.
  3. Register two repositories in one project and open an issue with the same number in each.
  4. Open a project's pipeline board at a viewport between 640px and 1024px.

Version: 0f646ac.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions