Skip to content

RFC-0056: CRCR Support for Nightly & Periodic CI#98

Draft
subinz1 wants to merge 9 commits into
pytorch:masterfrom
subinz1:rfc-0056-crcr-nightly-periodic-ci
Draft

RFC-0056: CRCR Support for Nightly & Periodic CI#98
subinz1 wants to merge 9 commits into
pytorch:masterfrom
subinz1:rfc-0056-crcr-nightly-periodic-ci

Conversation

@subinz1

@subinz1 subinz1 commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Summary

Proposes extending the Cross-Repository CI Relay (CRCR) to support nightly and periodic CI for downstream repositories using an authenticated self-report model.

Currently, CRCR only dispatches on pull_request and push events. Nightly/periodic runs have no upstream trigger — the state machine rejects callbacks without a prior DISPATCHED record. This RFC proposes a simpler path: downstream repos self-schedule via cron, fetch the target SHA, run CI, and report results directly to the relay in a single callback (no in_progress state, no Redis, no state machine).

Nightly Flow

The diagram below illustrates the nightly CI flow. Periodic CI follows the same pattern but fetches the SHA from main or viable/strict instead of the nightly branch. This will ensure that the results are aligned with upstream nightly results on HUD page.

flowchart TD
    A["⏰ Downstream cron schedule<br/>(e.g., daily 02:00 UTC)"] --> B["Fetch HEAD SHA from<br/>pytorch/pytorch nightly branch"]
    B --> C["Build & test PyTorch<br/>at that SHA"]
    C --> D["Single callback to relay<br/>dispatch_id = SHA<br/>event_type = nightly<br/>conclusion = success/failure"]
    D --> E{"Relay validates"}
    E -->|"OIDC token → repo on allowlist"| F["✅ Valid"]
    E -->|"SHA exists on pytorch/pytorch"| F
    F --> G["Direct upsert to DynamoDB<br/>(no Redis, no state machine)"]
    G --> H["DynamoDB → ClickHouse"]
    H --> I["📊 HUD display<br/>hud.pytorch.org/crcr"]
Loading

Callback model comparison

PR / push (existing) Nightly / periodic (new)
Trigger Upstream webhook → relay dispatches Downstream cron (self-triggered)
State machine DISPATCHED → IN_PROGRESS → COMPLETED No state machine — single callback
Callbacks Two: in_progress then completed One: completed only
Redis Required Not used
Validation GitHub webhook signature OIDC token + SHA existence

SHA sources

Event type Branch Source
nightly nightly Top-of-tree (updated daily by trigger_nightly_core.yml)
periodic main or viable/strict Top-of-tree on target branch

The dispatch_id is the commit SHA itself — idempotent, correlatable, and maps directly to github.com/pytorch/pytorch/commit/<sha> on HUD.

Replay & Recovery

Nightly/periodic callbacks are idempotent by design: the delivery_id is the upstream commit SHA, and the callback upserts into DynamoDB, so re-running the same workflow for the same SHA is safe and produces no duplicates.

Manual replay: Navigate to the downstream repo's Actions tab → select the nightly workflow → click "Run workflow" (workflow_dispatch). The workflow fetches the current nightly branch HEAD SHA, runs CI, and reports results via the callback action — identical to a cron-triggered run.

Automated missed-nightly detection (self-healing re-triggers) may be considered in a future iteration based on WG feedback.

Implementation PRs

Related RFCs

  • RFC-0050: Cross-Repository CI Relay (original dispatch + callback pipeline)
  • RFC-0054: HUD Integration for Out-of-Tree CI Results

Status

Draft — for CRCR Working Group discussion.

Proposes extending CRCR to support scheduled (nightly/periodic) CI
dispatches for downstream repositories. Presents two options —
EventBridge cron vs. upstream GitHub Actions workflow — with detailed
tradeoffs, implementation effort, and open questions for WG discussion.
@meta-cla meta-cla Bot added the cla signed label Jul 1, 2026
Comment thread RFC-0056-CRCR-Nightly-Periodic-CI.md Outdated
1. Fetch pytorch/pytorch main HEAD SHA via GitHub API
2. Build synthetic client_payload:
- event_type: "nightly" (or "periodic")
- delivery_id: "nightly-{date}-{uuid}"

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.

Suggestion: use the pytorch/pytorch main HEAD commit SHA as the dispatch/correlation ID instead of a random uuid — and avoid any trigger from pytorch/pytorch.

Option 2 already fetches the main HEAD SHA here (step 1). That fetch is a pull via the GitHub API on the relay's own EventBridge schedule — it needs no workflow_dispatch/repository_dispatch/webhook emitted by pytorch/pytorch. So the relay never depends on an event from torch; it just reads GET /repos/pytorch/pytorch/commits/main. That's a strong reason to prefer Option 2 over Option 1 (Option 1's whole premise is torch emitting the event).

Given the SHA is in hand, make it the ID rather than nightly-{date}-{uuid}:

  • Meaningful & correlatable — the ID is the tested commit, so HUD (and humans) can map a nightly run straight to github.com/pytorch/pytorch/commit/<sha>.
  • No torch coupling — derived entirely on the relay side.

Uniqueness for the Redis state machine (DISPATCHED → IN_PROGRESS → COMPLETED): a bare SHA can collide if main hasn't advanced between two scheduled runs, or on a manual re-trigger. Two clean options:

  1. SHA-keyed + idempotent (recommended): key on the bare SHA and skip re-dispatch if a DISPATCHED/COMPLETED record already exists for it. Bonus — you don't re-run nightlies when main didn't move.
  2. Composite if you want exactly one nightly per calendar day regardless of main movement: nightly-{sha}-{YYYYMMDD} (or -{run_id}). Keeps the SHA as the correlation key while guaranteeing a fresh state key per run.

HUD grouping (see the "HUD Changes" note near the end): this also lets you avoid the pr_number = 0 sentinel, which collapses every nightly into one bucket. Carry the SHA (already in payload.head_sha) and group non-PR runs by SHA — each nightly stays distinguishable and links to its commit. pr_number = 0 throws away that per-commit granularity.

Net design: EventBridge cron → relay reads main HEAD SHA → delivery_id = SHA (idempotent) → HUD correlates by commit — no event from pytorch/pytorch anywhere in the path.

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.

Sure @atalman

Also noting the differences in terms of changes between the option 1 and option 2 for clarity

Option A: EventBridge → Lambda

  1. Requires provisioning new AWS resources (EventBridge rules, targets, Lambda permissions, CloudWatch alarms) via change in Terraform.
  2. Adds a new Lambda entry point and a GitHub API dependency to fetch the HEAD SHA.
  3. Add Redis idempotency logic to avoid duplicate dispatches.
  4. Schedule changes require a Terraform deployment.

Option B: Reuse Existing Events from pytorch/pytorch workflow

  1. Adds a periodic trigger workflow in pytorch/pytorch
  2. Adds the event types in the lambda.
  3. Nightly uses the existing nightly branch push that already generates a webhook daily.
  4. The Lambda maps the ref name to a dispatch type; the SHA, delivery ID, and signature verification are all used from the current workflow.

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.

Per @atalman's review feedback — the main HEAD SHA is already fetched,
so use it as the dispatch/correlation ID. This makes the ID meaningful
and lets HUD correlate nightly runs directly to the tested commit.
@atalman

atalman commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Following up on the trigger discussion — I'd like to put a third option on the table that I think fits the nightly/periodic use case better than either Option A (EventBridge cron) or Option B (reuse existing events), because it removes the scheduled trigger from the critical path entirely.

Option C: Authenticated self-report

Instead of a central scheduler dispatching to downstream repos, let each downstream repo drive its own schedule and report results back. The relay stops being a trigger and becomes a validating ingest endpoint.

downstream cron → runs CI against pytorch/pytorch HEAD
  → dispatch_id = the tested pytorch/pytorch commit SHA
  → calls back to the relay
  → relay accepts IF:  (1) originating repo is on the allowlist
                       (2) the SHA is a real pytorch/pytorch commit
  → upsert record keyed by (repo, SHA) → HUD

This drops the "callback requires a prior DISPATCHED record" precondition and replaces it with authorization + hash-validity at callback time.

Why this is attractive

  • No trigger coupling anywhere — nothing in pytorch/pytorch emits an event, and there is no central EventBridge cron. Each downstream owns its own schedule (and gets workflow_dispatch manual re-trigger for free).
  • No new AWS infrastructure — no EventBridge rules, no Terraform, no scheduler Lambda path.
  • Coordination-free correlation — because the dispatch ID is the pytorch/pytorch HEAD SHA, any actor derives the same ID from the same commit with no central handoff. HUD groups by commit SHA (no pr_number=0 sentinel).

Two requirements to make it safe

  1. Repo identity must be cryptographic, not self-declared. Once we accept unsolicited callbacks, the relay's only security boundary is proving the caller really is an allowlisted repo. Use the GitHub Actions OIDC token — the signed repository claim maps to the allowlist, is short-lived, and is audience-scoped (also kills replay). A repo name in the payload or a shared secret is spoofable and should not be accepted.
  2. Validate the SHA exists in pytorch/pytorch (GET /repos/pytorch/pytorch/commits/{sha}, cached to avoid rate limits). Note this proves the SHA is real, not that CI actually ran against it — that's inherent to self-reporting and acceptable in a cooperative-partner model, but worth stating explicitly.

Explicit non-goal: missing-run detection

Unlike central dispatch, the relay never "expects" N callbacks, so it cannot tell "a repo's cron silently broke" from "a repo isn't participating." That's fine by design here — we only care about the signals that arrive; absence of a signal is not an error condition. This is what lets us drop all the completeness/liveness bookkeeping.

On SHA alignment

If we want rows from different backends to line up for side-by-side comparison, downstreams should pin the same stable daily ref (nightly / viable/strict) rather than resolving raw main (which drifts minute-to-minute). If each backend's signal stands on its own, this is optional. Composite key {sha}-{YYYYMMDD} only if we want to allow intentional re-runs on the same commit.

Net

This keeps the SHA-as-correlation-ID improvement already applied in this PR, and it satisfies "nightly/periodic must not require a workflow to be initiated near pytorch/pytorch" more completely than the reuse approach (which still needs a periodic trigger workflow in pytorch/pytorch). The cost is moving the trust boundary to OIDC-verified callbacks and accepting that missing signals are silent — both of which are acceptable for this use case.

subinz1 added 6 commits July 14, 2026 18:14
Adds Option C proposed by @atalman: downstream repos self-schedule via
their own crons and report results back through OIDC-validated callbacks.
Includes pros/cons analysis, implementation effort estimate, updated
comparison table, and open questions around state machine divergence,
trust model, and missing-run detection.
Remove Option 1 (upstream cron workflow) and Option 2 (EventBridge)
sections. Present only the authenticated self-report approach proposed
by @atalman, where downstream repos self-schedule nightly/periodic CI
and report results via OIDC-validated callbacks with SHA-keyed upserts.
Keep only the design structure and implementation details. Replace
open questions/drawbacks/alternatives with a concrete file changes
table showing exactly what needs to be modified.
Briefly document the two alternatives (EventBridge cron and upstream
workflow) that were evaluated before settling on the authenticated
self-report design, with reasons they were set aside.
Nightly runs fetch HEAD from pytorch/pytorch nightly branch (updated
daily by trigger_nightly_core.yml). Periodic runs use main or
viable/strict. dispatch_id is the commit SHA per @atalman's suggestion.
Includes SHA source table, example downstream workflow, and updated
flow diagram.
No in_progress state — downstream workflows report the final result
in one callback. No Redis state tracking, no zombie sweeper needed.
Adds comparison table showing PR/push vs nightly/periodic callback
models.
Document the manual replay procedure via workflow_dispatch.
Callbacks are idempotent (SHA-based delivery_id + upsert),
so re-running is safe. Automated detection deferred to WG.
atalman pushed a commit to pytorch/test-infra that referenced this pull request Jul 17, 2026
## Summary

Phase 1 of the CRCR nightly/periodic self-report implementation ([RFC
98](pytorch/rfcs#98)).

Adds a new code path in the callback Lambda for `nightly` and `periodic`
event types. Unlike PR/push callbacks which require a `DISPATCHED`
record in Redis and follow a two-step state machine (`in_progress` →
`completed`), nightly/periodic callbacks use a **single-callback
model**:

- Downstream repo self-triggers via cron
- Runs CI against a `pytorch/pytorch` SHA (from `nightly` branch or
`main`)
- Reports the final result in **one callback** — no `in_progress` step

**What's different from PR/push:**
- No Redis writes, no state machine, no zombie tracking
- Status must be `completed` (rejects `in_progress`)
- No upstream check runs (nightly results are informational only)
- Timing metrics (queue_time, execution_time) are `null` — no dispatch
record to measure against

**Changes:**
| File | Change |
|------|--------|
| `callback/callback_handler.py` | New `_handle_nightly_callback()` +
routing in `handle()` |
| `tests/test_callback_handler.py` | 8 new tests covering the nightly
path |

**Remaining phases:**
- Phase 2: Update callback action YAML with `delivery-id` / `event-type`
inputs
- Phase 3: Downstream cron workflow in `TorchedHat/pytorch-redhat-ci`
- Phase 4: ClickHouse query for nightly results
- Phase 5-6: HUD display

## Test plan
- [x] 8 unit tests: forward to HUD, skip Redis, no timing metrics,
periodic variant, reject `in_progress`, allowlist gating, rate limiting,
missing `delivery_id`
- [ ] CI passes on this PR
atalman pushed a commit to pytorch/test-infra that referenced this pull request Jul 17, 2026
…e 2) (#8303)

## Summary

Phase 2 of the CRCR nightly/periodic self-report implementation ([RFC
98](pytorch/rfcs#98)).

Extends the callback action
(`.github/actions/cross-repo-ci-relay-callback/action.yml`) to support
**self-report mode** for nightly/periodic workflows. Downstream repos
that self-trigger via cron (no upstream `repository_dispatch`) can now
report results by setting two new inputs:

- **`delivery-id`**: The upstream `pytorch/pytorch` commit SHA that was
tested — becomes the correlation key on the HUD
- **`event-type`**: `"nightly"` or `"periodic"`

When both are set, the action constructs the payload from scratch (no
`client_payload` needed). Existing PR/push behavior is completely
unchanged.

**Validation:**
- `event-type` must be `"nightly"` or `"periodic"`
- `status` must be `"completed"` (single-callback model, no
`in_progress` step)

**Example downstream usage:**
```yaml
- name: Report nightly results to CRCR
  if: always()
  uses: pytorch/test-infra/.github/actions/cross-repo-ci-relay-callback@main
  with:
    status: completed
    conclusion: ${{ job.status }}
    delivery-id: ${{ steps.sha.outputs.sha }}
    event-type: nightly
```

**Depends on:** Phase 1 (#8302) — callback Lambda handler for
nightly/periodic

## Test plan
- [ ] CI passes (actionlint)
- [ ] End-to-end test with Phase 3 downstream workflow
atalman pushed a commit to pytorch/test-infra that referenced this pull request Jul 17, 2026
…8304)

## Summary

Phase 1.2 of the CRCR nightly/periodic self-report implementation ([RFC
98](pytorch/rfcs#98)).

Adds `utils/sha_validator.py` — validates that a self-reported commit
SHA exists on `pytorch/pytorch` via the GitHub API before accepting
nightly/periodic callback results.

**Key features:**
- `validate_sha(upstream_repo, sha)` → `True` if SHA exists, `False` on
404, raises on other errors
- Module-level TTL cache (1 hour) avoids redundant API calls when
multiple downstream repos report against the same nightly SHA
- Expired entries are evicted lazily on each call

**Depends on:** Phase 1 (#8302) — will be called from
`_handle_nightly_callback()`

**Changes:**
| File | Change |
|------|--------|
| `utils/sha_validator.py` | New module: SHA validation with TTL cache |
| `tests/test_sha_validator.py` | 7 tests: valid/invalid SHA, cache
hit/miss, TTL expiry, error propagation |

## Test plan
- [x] 7 unit tests covering all paths
- [ ] CI passes
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants