Skip to content

feat: nightly mirror sweep of github-nango repos into shadow sync units - #5059

Merged
mbani01 merged 12 commits into
mainfrom
feat/issue-833-automated-mirroring
Oct 20, 2026
Merged

mbani01 merged 12 commits into
mainfrom
feat/issue-833-automated-mirroring

Conversation

@mbani01

@mbani01 mbani01 commented Oct 20, 2026

Copy link
Copy Markdown
Contributor

Why

Every github-nango repo moves to the connectors pipeline. The switch is automated (nightly sweep, #5049), but mirroring a repo into shadow sync units is still a hand-fed pod script. This adds the nightly job that keeps a bounded pool of repos in shadow, refilled as the sweep drains it, most active repos first, in batches the operator grows over time.

Design: docs/superpowers/specs/2026-10-20-issue-833-automated-mirroring-design.md (local).

What runs

Temporal schedule connectors-mirror-sweep on the connectors queue, daily at 03:00Z (after the 00:00Z diff and the 02:00Z switch sweep), overlap SKIP, 2-hour execution timeout. Workflow mirrorSweep({ batchSize, shadowCap }):

  1. inShadow = distinct repos with at least one non-emitting, non-decommissioned unit on a live integration.
  2. room = max(0, min(batchSize, shadowCap − inShadow)); zero → summary only.
  3. Candidates = active github-nango mappings with no sync units for that repo in that integration (name match), ordered enabled/not archived/not excluded first, then 30-day activity from Tinybird activityRelations (uniqExact(activityId) per channel), then owner/repo.
  4. Mirror the top room repos in chunks of 100, 5 GitHub resolutions in flight, through the same mirrorRepo path the pod script now uses: credential → Nango connection → installation token → resolve repo on GitHub → eight shadow units with since = start of the UTC day 48 h back. emitEnabled stays false.
  5. One summary log line per run.

Knobs (schedule input, no deploy)

  • batchSize: new repos per night. Starts at 500. Raise it as the diff and the sweep hold up:
    temporal schedule update --schedule-id connectors-mirror-sweep --type mirrorSweep --task-queue connectors --input '{"batchSize":1000,"shadowCap":5000}' --calendar '{"hour":3,"minute":0}' --overlap-policy Skip --catchup-window 1h --execution-timeout 2h
    
    Schedule creation in the worker swallows ScheduleAlreadyRunning and does not update, so operator-set values survive deploys.
  • shadowCap: repos in shadow at once, 5,000. Never exceeded.

Skips

A repo that cannot be mirrored is logged once per run with event: mirror_repo_skipped and one reason: no_installation_id, token_mint_failed, repo_not_found, repo_renamed (GitHub redirects to a different name; mirroring it under the new URL would never be switched, fix the mapping by hand), github_error, nango_error, already_mirrored (units already exist for that node id; the upsert would rewind the watermark). Credential lookup errors (DB outage, missing GitHub App secret) are not skips: they fail the chunk, the run stops at that chunk with chunksFailed: 1, and the next night refills. A malformed schedule input (non-positive or non-integer values, batchSize > shadowCap) fails the workflow immediately instead of running as a silent no-op. Skips are not stored: a broken repo is retried and logged every night until fixed. The pod script script:mirror-nango-repos shares the path and now exits 1 instead of re-upserting when a repo is already mirrored.

Observability

  • event: mirror_sweep_summary — batchSize, shadowCap, inShadow, candidates, attempted, mirrored, skipped, unitsInserted, chunksFailed, durationMs.
  • event: mirror_repo_skipped — integrationId, owner, repoName, reason, message.
  • Progress over time from integration.sync_units (Metabase).

Rollout

  • Needs feat: diff 50 shadow channels at a time with a four-hour run budget #5055 (shadow-diff fan-out 50, 4 h timeout) deployed first: the first 500-repo night brings the diff to ~610 channels, past the old 30-minute timeout.
  • The connectors-worker deployment needs CROWD_TINYBIRD_ACTIVITIES_TOKEN (and CROWD_TINYBIRD_BASE_URL if not default). The Tinybird query has not been run against prod from this branch; the first scheduled run fails loudly if it is wrong and mirrors nothing.
  • The switch sweep is serial today (~40 s/repo); it may still be running at 03:00Z, so the pool refills a night later than the pace suggests. Sweep concurrency is a separate PR.
  • Expected pace from a 10-22 deploy with batch size doubling every second night: cap binds ~10-28, then ~1,200–1,700 repos/night as the sweep drains; all mirrored ~11-03.

Known rough edges

  • If a chunk activity is retried after a partial failure, repos mirrored on the first attempt are counted as already_mirrored skips on the retry, so that night's mirrored/unitsInserted under-count. Data is unaffected.
  • The lockfile re-resolves axios 1.19 → 1.20 for @sendgrid (the catalog pins ^1.20.0); incidental to adding axios to the worker.

Not in this PR

Sweep concurrency; a stored channel name in the already_mirrored message; ON CONFLICT DO NOTHING insert variant (the guard + the candidate query cover the sweep; a same-second rerun of the script and the sweep on one repo could rewind a same-day since).

Refs linuxfoundation/lfx-cdp-insights-ops#833

Copilot AI balanced review requested due to automatic review settings October 20, 2026 13:15
@cursor

cursor Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

PR Summary

Medium Risk
Writes integration sync units and calls GitHub/Nango at scale on a schedule; misconfiguration or Tinybird/query failures can block mirroring, but bounds and skip handling limit blast radius.

Overview
Adds an automated nightly mirror sweep so github-nango repos are copied into shadow sync_units without running a pod script, keeping a capped pool that the switch sweep can drain.

A Temporal schedule (connectors-mirror-sweep, 03:00Z) runs workflow mirrorSweep with batchSize / shadowCap. It counts repos already in shadow, picks unmirrored Nango mappings (prioritizing active repos and 30-day GitHub activity from Tinybird), then mirrors in chunks of 100 with bounded GitHub concurrency. Shared mirrorRepo handles Nango → token → channel resolve → eight incremental shadow units (48h lookback), with structured skip reasons and summary logging.

Data layer: listUnmirroredNangoRepos, countShadowRepos, and hasSyncUnitsForChannel support selection and deduplication. The script:mirror-nango-repos CLI is refactored onto mirrorRepo and exits non-zero on skips. Connectors worker gains @crowd/database (Tinybird) and axios for error classification.

Reviewed by Cursor Bugbot for commit 5a0ad0e. Bugbot is set up for automated code reviews on this repo. Configure here.

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Duplicate mappings can consume batch capacity, and case-variant channel counts can produce incorrect activity rankings.

2 open issues
1 file not reviewed
  • pnpm-lock.yaml: Generated file
What changed in this PR

Automates nightly mirroring of github-nango repositories into shadow sync units, preparing them for migration to the connectors pipeline.

Changes:

  • Adds a scheduled sweep with batch limits, activity-based prioritization, and summary logging.
  • Shares repository mirroring logic with the operator script.
  • Adds database queries and tests for selection, mirroring, and workflow behavior.
File Description
services/​libs/​data-access-layer/​src/​integrations/​nangoMapping.test.ts Tests candidate selection.
services/​libs/​data-access-layer/​src/​integrations/​index.ts Queries unmirrored mappings.
services/​libs/​data-access-layer/​src/​connectors/​syncUnits.ts Adds shadow counts and existence checks.
services/​libs/​data-access-layer/​src/​connectors/​syncUnits.test.ts Tests counts and existence checks.
services/​apps/​connectors_worker/​tsconfig.json References the database library.
services/​apps/​connectors_worker/​src/​workflows/​mirrorSweep.ts Orchestrates bounded mirror batches.
services/​apps/​connectors_worker/​src/​workflows/​mirrorSweep.test.ts Tests workflow limits and failures.
services/​apps/​connectors_worker/​src/​workflows.ts Exports the sweep workflow.
services/​apps/​connectors_worker/​src/​schedules/​mirrorSweep.ts Registers the nightly schedule.
services/​apps/​connectors_worker/​src/​mirrorSweepLimits.ts Defines capacity and chunk helpers.
services/​apps/​connectors_worker/​src/​mirrorSweep.ts Defines ranking, types, and skip classification.
services/​apps/​connectors_worker/​src/​mirrorSweep.test.ts Tests ranking and helpers.
services/​apps/​connectors_worker/​src/​mirrorRepo.ts Implements shared repository mirroring.
services/​apps/​connectors_worker/​src/​mirrorRepo.test.ts Tests mirroring and skip conditions.
services/​apps/​connectors_worker/​src/​main.ts Registers the schedule at startup.
services/​apps/​connectors_worker/​src/​githubChannelActivity.ts Reads activity totals from Tinybird.
services/​apps/​connectors_worker/​src/​githubChannelActivity.test.ts Tests activity query handling.
services/​apps/​connectors_worker/​src/​bin/​mirror-nango-repos-to-shadow-sync-units.ts Reuses shared mirroring logic.
services/​apps/​connectors_worker/​src/​activities/​mirrorSweepActivities.ts Implements selection, execution, and logging.
services/​apps/​connectors_worker/​src/​activities/​mirrorSweepActivities.test.ts Tests selection and concurrency limits.
services/​apps/​connectors_worker/​src/​activities.ts Exports sweep activities.
services/​apps/​connectors_worker/​package.json Adds dependencies and script entry.
pnpm-lock.yaml Updates dependency resolutions.

Review effort: Balanced

Comment thread services/apps/connectors_worker/src/githubChannelActivity.ts Outdated
Comment thread services/libs/data-access-layer/src/integrations/index.ts

@cursor cursor 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.

Stale Bugbot comment from a previous run.

Comment thread services/apps/connectors_worker/src/githubChannelActivity.ts
Copilot AI balanced review requested due to automatic review settings October 20, 2026 13:23

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Retry overlap remains unresolved, and rollout requires human confirmation of the prerequisite deployment and production Tinybird query.

No open issues 🎉

1 previously missed issue

In code that hasn't changed since last review

Medium severity Drain sibling repo calls before retrying the activity

services/​apps/​connectors_worker/​src/​activities/​mirrorSweepActivities.ts:56

Wait for every repo in the group to settle before failing the activity. If a credential lookup or insert rejects, Promise.all rejects while sibling calls continue. If those calls outlast the retry backoff, Temporal can retry the same chunk alongside them, exceeding the five-repo concurrency limit. Overlapping attempts can also both pass the channel-existence guard; upsertSyncUnits overwrites watermarks on conflict. Drain the pending calls before rethrowing the original error.

2 resolved since last review
1 file not reviewed
  • pnpm-lock.yaml: Generated file

Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings October 20, 2026 13:28

@cursor cursor 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.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 597afee. Configure here.

Comment thread services/apps/connectors_worker/src/githubChannelActivity.ts Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

The unresolved retry race and fleet-wide rollout prerequisites require human review before approval.

No open issues 🎉

1 previously missed issue

In code that hasn't changed since last review

Medium severity Prevent early Promise.all rejection from overlapping activity retries

services/​apps/​connectors_worker/​src/​activities/​mirrorSweepActivities.ts:56

Wait for every operation in the group to settle before failing the activity. If a credential or database lookup rejects while peers are awaiting Nango or GitHub, Promise.all rejects immediately but leaves those peers running. Temporal can retry the chunk before they finish, exceeding the five-repository concurrency limit and racing the channel-id check and watermark-updating upsert, even with unique mappings. Use Promise.allSettled and propagate failures only after all peers finish; this prevents overlap caused by an early rejection.

1 file not reviewed
  • pnpm-lock.yaml: Generated file

Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings October 20, 2026 13:34

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Retry concurrency needs correction, and production Tinybird validation and the prerequisite shadow-diff deployment still require human confirmation.

No open issues 🎉

1 previously missed issue

In code that hasn't changed since last review

Medium severity Await all mirror operations before retrying to prevent overlap

services/​apps/​connectors_worker/​src/​activities/​mirrorSweepActivities.ts:56

If a credential lookup or database operation rejects, Promise.all fails immediately while the other mirrors keep running. If those calls outlast the retry delay, the next activity attempt can exceed the five-repository concurrency limit and race the same repositories through the existence check and watermark-updating upsert. Await all started mirrors with Promise.allSettled before propagating a failure so retries cannot overlap unfinished work from this failure path.

1 file not reviewed
  • pnpm-lock.yaml: Generated file

Review effort: Balanced

@mbani01 mbani01 self-assigned this Oct 20, 2026
Copilot AI balanced review requested due to automatic review settings October 20, 2026 16:23
@mbani01
mbani01 merged commit cf51e98 into main Oct 20, 2026
8 checks passed
@mbani01
mbani01 deleted the feat/issue-833-automated-mirroring branch October 20, 2026 16:23

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Retry overlap needs correction, and the production Tinybird query and fleet-scale rollout still require human validation.

1 open issue
1 file not reviewed
  • pnpm-lock.yaml: Generated file

Review effort: Balanced

Comment on lines +56 to +57
const outcomes = await Promise.all(group.map((candidate) => mirrorRepo(qx, candidate, since)))
outcomes.forEach((outcome, i) => {
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.

3 participants