fork-sync-all

Built with Ona

Sync and mirror infrastructure for the three-org chain:

Interested-Deving-1896  ──►  OpenOS-Project-OSP  ──►  OpenOS-Project-Ecosystem-OOC
        ▲                                                         │
        └─────────── upstream-commits / upstream-prs ────────────┘
                                    │
                                    ▼
                         GitLab openos-project
                    (11 subgroups, ~150 repos mirrored)

Documentation


Workflows

Sync & Mirror

WorkflowScheduleWhat it does
sync-forks.ymlDaily 06:00Syncs all Interested-Deving-1896 forks with their upstreams
sync-pieroproietti-forks.ymlEvery 4h :05Fast-path sync for pieroproietti forks only
mirror-to-osp.ymlEvery 6h :00Mirrors Interested-Deving-1896 repos into OpenOS-Project-OSP
mirror-osp-to-gitlab.ymlEvery 4h :30Mirrors OpenOS-Project-OSP repos into GitLab openos-project
sync-from-gitlab.ymlDaily 04:22Pulls GitLab openos-project repos back into Interested-Deving-1896 (scheduled fallback; primary trigger is GitLab CI on push)
sync-registered-imports.ymlEvery 6h :55Re-syncs all repos registered via the import workflow

Import

WorkflowTriggerWhat it does
import-repo.ymlManualImports any git repo from any platform into Interested-Deving-1896

Import workflow inputs:

  • repo_url — source URL (GitHub, GitLab, Bitbucket, Codeberg, Sourcehut, Gitea, or any git host)
  • repo_name — optional rename in Interested-Deving-1896 (defaults to source name)
  • mirror_to_osp_ooc — push through the OSP → OOC chain immediately
  • ongoing_sync — register in registered-imports.json for re-sync every 6h

Quota and queue management

WorkflowScheduleWhat it does
full-chain-flush.ymlOn validate-config success / manualMaster orchestrator — runs the full mirror chain in sequence
queue-manager.ymlEvery 15 minDeduplicates queued runs; evicts runs queued > 25 min
quota-reserve.ymlEvery 10 minCancels low-priority queued runs when quota < 1000
validate-config.ymlOn push / PRValidates all config files; runs AgentShield security scan (opt-in)

Security and token management

WorkflowScheduleWhat it does
token-health.ymlWeekly Monday 09:00Checks GitHub + GitLab PAT expiry; opens an issue at 45 days out
rotate-token.ymlManualRotates any repo secret via workflow dispatch

Maintenance

WorkflowScheduleWhat it does
reconcile-org-refs.ymlManual / on pushRewrites org names in file content across all three orgs
upstream-commits.ymlEvery 6h :47Detects direct commits to OSP/OOC and opens PRs in Interested-Deving-1896
upstream-prs.ymlEvery 6h :33Syncs open PRs from OSP/OOC upstream into Interested-Deving-1896
add-mirror-repo.ymlManualAdds a new repo to the OSP + OOC mirror chain
setup-osp-mirrors.ymlManualInjects mirror-osp-to-ooc.yaml into all OSP repos
resolve-failures.ymlDaily 07:30AI-assisted CI failure resolver (GitHub Models)
upstream-workflow-proposal.ymlWeekly Monday 06:00Scans OSP-bound repos for new workflows; opens a PR to propose as a template skeleton
rebase-lts.ymlWeeklyRebases the lts branch of penguins-eggs
sync-eggs-docs-to-book.ymlOn pushSyncs penguins-eggs docs into penguins-eggs-book
mirror-artifacts.ymlScheduledMirrors release artifacts (packages, containers, flatpaks)
ota-discover.ymlScheduledDiscovers OTA update payloads across OSP-bound repos

Secrets

SecretUsed byNotes
SYNC_TOKENAll workflowsGitHub PAT — repo + workflow + admin:org scopes
GH_SYNC_TOKENGitLab CI sync-from-gitlab jobSame PAT stored as a GitLab CI variable
GITLAB_SYNC_TOKENmirror-osp-to-gitlab.yml, sync-from-gitlab.ymlGitLab PAT — api + write_repository on openos-project group
BITBUCKET_TOKENimport-repo.yml, sync-registered-imports.ymlBitbucket app password (private repos only)
GITEA_TOKENimport-repo.yml, sync-registered-imports.ymlGitea/Codeberg PAT (private repos only)
ADD_MIRROR_REPO_SYNCadd-mirror-repo.ymlScoped PAT for repo creation
ACTIVITYSMITH_API_KEYfull-chain-flush.ymlOptional — live activity tracking; skipped if unset
ANTHROPIC_API_KEYvalidate-config.ymlOptional — AgentShield security scan; skipped if unset

To add a missing secret, run in your terminal (value prompted securely, never logged):

gh secret set <SECRET_NAME> --repo Interested-Deving-1896/fork-sync-all

Registered Imports

registered-imports.json tracks repos imported via import-repo.yml with ongoing_sync enabled. The sync-registered-imports.yml workflow reads this file hourly and re-pulls each source.

Schema:

[
  {
    "source_url":  "https://gitlab.com/some-group/some-repo",
    "target_name": "some-repo",
    "platform":    "gitlab",
    "added":       "2026-05-02T18:00:00Z"
  }
]

To register a repo manually, run import-repo.yml with ongoing_sync: true, or edit the file directly and commit.


Rate limits

All workflows share a single SYNC_TOKEN. Understanding the limits prevents surprise failures and helps diagnose them when they do occur.

GitHub REST API

Limit typeThresholdResetHeader
Primary (per token)5 000 req/hrTop of the hourX-RateLimit-Reset (epoch)
Secondary (burst/concurrency)No fixed number — triggered by rapid sequential requests~60 s cooldownX-RateLimit-Reset or Retry-After
Unauthenticated60 req/hr per IPTop of the hourX-RateLimit-Reset

What a 403/429 means here: GitHub returns HTTP 403 for secondary rate limits and HTTP 429 for primary exhaustion. Both include X-RateLimit-Reset in the response headers. All scripts that call the GitHub API read this header and sleep until the reset window opens before retrying (up to 3 attempts).

Workflows most likely to hit limits: sync-forks.yml (scans all forks), reconcile-org-refs.yml (reads every file in every repo), and resolve-failures.yml (scans all repos across three orgs). These run sequentially within their own concurrency group so they don't compound each other's usage.

If a workflow fails with "API rate limit exceeded": the next scheduled run will succeed once the window resets. resolve-failures.yml will also catch and retry it automatically. No manual intervention is needed unless the token itself has been revoked.

GitHub Models API

Used by resolve-failures.yml and create-readmes.yml / update-readmes.yml for AI-assisted analysis and generation.

Limit typeBehaviourHeader
Per-token quotaVaries by model; gpt-4o-mini has the highest allowanceRetry-After (seconds)
Rate (requests/min)Model-dependentRetry-After

HTTP 429 from the Models API includes a Retry-After header. Scripts read this and sleep for the indicated duration before retrying (up to 3 attempts). If the quota is fully exhausted the script logs [models-rate-limit] GitHub Models quota exhausted and skips AI analysis for that run — the workflow still exits 0 so it doesn't generate a false failure notification.

GitLab API

Used by mirror-osp-to-gitlab.yml, sync-from-gitlab.yml, and sync-to-gitlab.yml.

Limit typeThresholdResetHeader
Authenticated REST2 000 req/min per tokenPer-minute windowRateLimit-Reset (epoch)
Unauthenticated500 req/min per IPPer-minute windowRateLimit-Reset

HTTP 429 (and occasionally 403) from GitLab includes a RateLimit-Reset header. Scripts read this and sleep until the window resets before retrying.

git push limits

Mirror scripts that push via HTTPS (mirror-to-osp.yml, mirror-osp-to-ooc.yaml, sync-to-gitlab.yml, sync-registered-imports.yml, etc.) can hit transient push rejections under load — these are not HTTP API limits but git-level errors. All push steps retry up to 3 times with linear backoff (15 s, 30 s, 45 s) before failing.

The mirror-osp-to-ooc.yaml workflow additionally uses a concurrency group (mirror-to-ooc) so concurrent runs queue rather than race, which eliminates the cannot lock ref class of push failures.

Diagnosing a rate-limit failure

  1. Open the failed run log and search for [rate-limit] or rate limit exceeded.
  2. The log line includes the HTTP status, sleep duration, and attempt number.
  3. If all 3 retries were exhausted the next scheduled run will succeed automatically — primary limits reset hourly, secondary limits within ~60 s.
  4. If failures persist across multiple scheduled runs, check that SYNC_TOKEN is valid (gh auth status) and has the required scopes (repo, workflow, admin:org).

GitHub Actions runner minutes

Free tier: 2,000 min/month (Linux, resets 1st of each month). All jobs use ubuntu-latest (1× multiplier). At the current schedule density (~7 hourly workflows), this repo exceeds the free tier. A paid plan or self-hosted runner is required.

Symptoms of exhaustion: ubuntu-latest jobs queue indefinitely, 0 in-progress runs, no runners active. Check via Settings → Billing → Actions.

Recovery: Cancel all queued runs (they will never start), then wait for the monthly reset or add a self-hosted runner.

# Bulk cancel all queued runs (requires API quota)
gh api "repos/Interested-Deving-1896/fork-sync-all/actions/runs?per_page=100" \
  --jq '[.workflow_runs[] | select(.status=="queued") | .id] | .[]' | \
  xargs -I{} gh api -X POST \
    "repos/Interested-Deving-1896/fork-sync-all/actions/runs/{}/cancel"

Concurrency groups and stuck runs

All workflows use cancel-in-progress: true. A newer run always supersedes a queued one, preventing permanent queue buildup when runner minutes are exhausted mid-job.

Detecting stuck runs:

gh api "repos/Interested-Deving-1896/fork-sync-all/actions/runs?per_page=100" \
  --jq '[.workflow_runs[] | select(.status=="queued")] | length'

workflow_run trigger cost

workflow_run fires on every completed event regardless of conclusion. All listeners in this repo gate at the job level so they exit immediately (no runner cost) when the upstream conclusion doesn't match:

  • Content processors (create-readmes, update-readmes, inject-badges, translate-readmes, lts-readmes, mirror-osp-to-gitlab): gate on conclusion == 'success'
  • Watchdogs (mirror-orgs-watchdog): gate on conclusion == 'failure'

See DOCS/OPERATIONS.md for the full operational reference: quota tables, schedule summary, self-hosted runner setup, and quick-reference reset times.

GitLab subgroups

11 subgroups under gitlab.com/openos-project, ~150 repos mirrored:

SubgroupReposFocus
git-management_deving4Git tooling and org management
penguins-eggs_deving3penguins-eggs distro tools
immutable-filesystem_devingvariesImmutable filesystem projects
linux-kernel_filesystem_deving15Kernel and filesystem repos
incus_devingvariesIncus container/VM tooling
taubyte_deving8Taubyte protocol repos
neon-devingvariesKDE Neon repos
ops5Infrastructure and tooling
yaml-tooling_deving29YAML tools, linters, schema validators, GH Actions tooling
cachyos_deving15CachyOS distro packages
ai-agents_deving12AI agent frameworks and tools

Subgroup IDs and repo assignments are in config/gitlab-subgroups.yml.


Mirror chain timing

:00  mirror-to-osp.yml          Interested-Deving-1896 → OSP
:05  sync-pieroproietti          pieroproietti forks fast-path
:10  quota-reserve.yml           Cancel low-priority runs if quota < 1000
:15  queue-manager.yml           Deduplicate queued runs
:15  mirror-osp-to-ooc.yaml      OSP → OOC  (per-repo, injected by setup-osp-mirrors)
:23  upstream-prs.yml            OOC/OSP PRs → Interested-Deving-1896
:30  mirror-osp-to-gitlab.yml    OSP → GitLab openos-project
:45  upstream-commits.yml        Direct OSP/OOC commits → PRs in Interested-Deving-1896
:55  sync-registered-imports     External platform imports re-sync

Full chain (validate-config → full-chain-flush) runs on every push to main.

AGENTS.md

Conventions, patterns, and known pitfalls for AI agents working in this repo.


Repository overview

fork-sync-all is the control plane for the Interested-Deving-1896 GitHub org. It mirrors repos into OpenOS-Project-OSP (GitHub) and then to openos-project (GitLab), manages READMEs across ~49 OSP-bound repos, syncs upstream forks, and runs org-wide maintenance workflows.

Key config files:

  • config/gitlab-subgroups.yml — single source of truth for GitLab subgroup placement
  • registered-imports.json — upstream repos to keep in sync
  • scripts/ — all automation scripts
  • .github/workflows/ — GitHub Actions workflows

Key directories:

  • vendor/ — third-party components hosted/deployed by fork-sync-all (e.g. infra-dashboard). Everything in scripts/ is first-party automation. Do not move scripts into vendor/.

GitHub API quota

Both GH_TOKEN and SYNC_TOKEN belong to the same user (ID 202036334) and share the same 5000 req/hr REST bucket. Treat them as one pool.

  • raw.githubusercontent.com fetches do not count against the quota
  • GraphQL counts as 1 call regardless of how many repos are queried
  • The quota pre-flight in workflows uses MIN_QUOTA (typically 1000–1500) to skip runs when the bucket is too low; quota-monitor.sh retries after reset

When quota is at 0, avoid any gh api, curl .../api.github.com/..., or gh_get calls. Check reset time with:

curl -sf -H "Authorization: token $SYNC_TOKEN" \
  "https://api.github.com/rate_limit" | jq '{remaining, reset: (.resources.core.reset | todate)}'

Script conventions

All logging helpers must write to stderr

Every script defines some combination of info(), warn(), dry(), and log(). All must use >&2:

info() { echo "[script-name] $*" >&2; }
warn() { echo "[warn] $*" >&2; }
dry()  { echo "[dry-run] $*" >&2; }
log()  { echo "[$(date -u '+%H:%M:%S')] $*" >&2; }

Why this matters: Several functions are called inside $(...) subshell captures where their stdout becomes the captured value (e.g. README content, repo lists, API responses). Any logging call without >&2 inside such a function will corrupt the captured data.

This applies to includes/gh-api.sh too — merge_upstream() status messages must go to stderr since callers may capture its output via result=$(merge_upstream ...).

Known functions called inside $(...) captures — never emit to stdout inside these:

  • rewrite_readme() in update-readmes.sh
  • fill_missing_sections() in update-readmes.sh
  • build_readme() in create-readmes.sh
  • generate_*() functions in update-readmes.sh
  • merge_upstream() in scripts/includes/gh-api.sh

YAML parsing

Always use yaml.safe_load — never hand-rolled regex/indent parsers:

import yaml
with open(config_path) as f:
    config = yaml.safe_load(f)
subgroups = config.get("subgroups", {}) or {}

This applies to gitlab-subgroups.yml parsing in all scripts.

includes/ scripts

scripts/includes/budget.sh, scripts/includes/gh-api.sh, and scripts/includes/quota-instrument.sh are sourced by many scripts and workflows. Changes there have broad impact.

  • budget.sh — provides budget_init, budget_check, budget_report, osp_priority_repos, and workflow_min_quota. The latter reads per-workflow min_quota from config/workflow-quota-costs.yml.
  • gh-api.sh — provides gh_api, gh_get, gh_api_graphql, merge_upstream, get_default_sha. All status messages use >&2. Guard against double-sourcing is in place (_GH_API_LOADED). gh_get URL is a convenience GET wrapper around gh_api with full retry and reset-aware backoff — the canonical implementation that individual scripts should migrate to (see consolidation note below).

gh_get / gh_api consolidation (complete)

All scripts now source includes/gh-api.sh for gh_get. The three tiers that existed during migration have been fully consolidated:

TierScriptsStatus
Full retry (canonical)check-osp-ci.sh, cleanup-branches.sh✅ migrated
No retry, fail-fastcreate-readmes.sh, inject-badges.sh, pre-flush-prep.sh, readme-wizard.sh, rebase-prs.sh, sync-template.sh, update-readmes.sh✅ migrated
No retry, silent failrerun-after-rate-limit.sh, scan-rate-limit-failures.sh✅ migrated (added || echo '{}' fallbacks on capture sites)

All new scripts should source includes/gh-api.sh and use gh_get directly. Do not define a local gh_get() in any new script.

  • quota-instrument.sh — provides qi_begin / qi_end for measuring REST quota consumption per workflow run. Wire into the main job step of any workflow you want to instrument. Writes a structured HTML comment to GITHUB_STEP_SUMMARY that update-quota-costs.yml parses weekly to compute observed p50/p95 values.

REST → GraphQL conversion

Prefer GraphQL over paginated REST for any loop that fetches the same data for multiple repos. GraphQL counts as 1 REST call regardless of how many repos are queried.

Standard pattern for org repo lists:

result=$(curl -sf \
  -H "Authorization: token ${GH_TOKEN}" \
  -H "Content-Type: application/json" \
  "${GH_API}/graphql" \
  -d "{\"query\":\"{ organization(login: \\\"${ORG}\\\") { repositories(first: 100) { nodes { name } pageInfo { hasNextPage endCursor } } } }\"}" \
  2>/dev/null || echo "{}")
echo "$result" | python3 -c "
import json,sys
d=json.load(sys.stdin)
for n in d.get('data',{}).get('organization',{}).get('repositories',{}).get('nodes',[]):
    print(n['name'])
" 2>/dev/null

Prefetch pattern for per-repo metadata (existence, pushedAt, README): Batch all repos into a single GraphQL call using aliases, populate an associative array, then read from the cache in the loop — zero REST calls per repo:

declare -A _REPO_EXISTS=()
# ... build aliases, fire one GraphQL call, populate _REPO_EXISTS ...
# In the loop:
[[ -z "${_REPO_EXISTS[$repo]:-}" ]] && continue  # skip non-existent repos

See sync-registered-imports.sh (prefetch_repo_metadata), mirror-releases.sh (prefetch_upstream_existence), and inject-badges.sh (list_gh_repos + _README_CACHE) for reference implementations.

What cannot be converted to GraphQL:

  • check-runs and statuses endpoints — not exposed in GraphQL
  • actions/workflows and actions/secrets — not in GraphQL
  • Write operations (create repo, push file, cancel run) — REST only

Tree fetches

Use ?recursive=1 on the git trees endpoint to get all file paths in one call, then check membership with grep -qxF before fetching individual files:

tree_json=$(gh_get "${GH_API}/repos/${owner}/${repo}/git/trees/HEAD?recursive=1")
tree_paths=$(echo "$tree_json" | jq -r '.tree[] | select(.type=="blob") | .path')
echo "$tree_paths" | grep -qxF "package.json" && # file exists, fetch it

Never probe file existence with per-file /contents/ calls in a loop.

YAML-safe shell in run: blocks

GitHub Actions run: blocks are YAML block scalars. The YAML parser processes the file before the shell runner sees it, so certain shell constructs break parsing even though they would be valid bash.

Patterns that break YAML — never use these inside run: blocks:

PatternWhy it breaksFix
VAR=" with newline before closing "Opens an unclosed YAML flow scalarUse printf or write to a temp file
python3 -c " with newline before closing "Same — unclosed flow scalarCollapse to a single-line -c invocation
--- on its own lineYAML document separatorUse ---- or printf '\xe2\x80\x94' for em dash
Heredoc end-marker that is a bare YAML keyword (YAML, EOF, END) at column 0Parsed as a bare mapping keyRename to OTA_CONFIG_EOF, PYEOF, etc. — anything not a YAML keyword
Multi-line git commit -m "..."Unclosed flow scalarUse $'subject\n\nbody' ANSI-C quoting or chained -m flags

Safe alternatives:

# Multi-line python: collapse to one line
repos=$(python3 -c "import yaml; d=yaml.safe_load(open('config/x.yml')); print(' '.join(d.get('repos',[])))")

# Multi-line variable: use printf into a temp file
printf 'line1\nline2\n' > /tmp/body.txt

# Multi-line commit message: ANSI-C quoting
git commit -m $'subject\n\nbody line 1\nbody line 2'

# Or chained -m flags (each becomes a paragraph)
git commit -m "subject" -m "body paragraph"

# Heredoc end-marker: use a non-YAML-keyword name
cat > file.yml << 'CONFIG_EOF'
...
CONFIG_EOF

The validator catches these: python3 scripts/validate-workflow-guards.py runs a YAML parse check across all 75 workflow files. Run it after editing any workflow. The full-suite parse check is also embedded in validate-config.yml.


Workflow patterns

Queue and quota management

Three workflows protect the system from quota exhaustion cascades and runner starvation:

WorkflowSchedulePurpose
queue-manager.ymlEvery 30 min + after rate-limit-rerunDeduplicates queued runs (keeps newest per workflow) and evicts runs queued > 25 min
quota-reserve.ymlEvery 30 min + after rate-limit-rerunCancels low-priority queued runs when quota drops below 1000. Uses per-workflow min_quota from config/workflow-quota-costs.yml for cost-aware cancellation.
critical-deploy.ymlManual onlyFast-lane: commit + push → aggressive queue clear → priority dispatch

Priority tiers — single source of truth in config/workflow-priority-tiers.yml:

  • Tier 1 CRITICAL — never cancelled (token rotation, queue/reserve management, config validation)
  • Tier 2 HIGH — mirror chain, sync operations
  • Tier 3 MEDIUM — READMEs, CI checks (default for unknown workflows)
  • Tier 4 LOW — translation, dep graph, maintenance (cancelled first)

When adding a new workflow, add it to both:

  1. config/workflow-priority-tiers.yml — by workflow name: field (not filename). Both queue-manager.sh and quota-reserve.sh load tiers from this file at runtime — no script edits needed.
  2. config/workflow-sync.yml — under github_only (most workflows) or paired (if it has a GitLab CI counterpart). validate-workflow-guards.py warns on any workflow file not listed in either section.

Run python3 scripts/validate-workflow-guards.py after adding any workflow to confirm zero warnings.

dispatch-and-wait.sh exit codes:

  • 0 — workflow completed successfully
  • 1 — workflow failed or timed out
  • 2 — workflow was cancelled (by queue-manager or manually) — retriable, not a real failure

full-chain-flush.yml and critical-deploy.sh both handle exit 2 with a warning rather than aborting.

Concurrency groups

All workflows triggered by schedule or workflow_run must have a concurrency group to prevent queue pile-ups:

concurrency:
  group: workflow-name
  cancel-in-progress: true

workflow_run triggers

Each workflow should have at most one workflow_run upstream trigger. Multiple triggers cause fan-out: N completions × M downstream workflows = queue explosion.

Every name in workflow_run.workflows: must exactly match the name: field of a workflow file that actually exists in .github/workflows/. A phantom name causes the trigger to fire on every push but the job fails immediately — GitHub cannot resolve the upstream workflow. validate-workflow-guards.py (Check 5) catches this automatically.

Quota pre-flight

All hourly/daily/frequent workflows include a quota pre-flight step before doing any API work. The step sets skip=true when remaining < MIN_QUOTA and subsequent steps check if: steps.quota.outputs.skip == 'false'.

Quota cost registry

config/workflow-quota-costs.yml is the single source of truth for per-workflow REST call cost estimates. It drives:

  • quota-reserve.sh — cost-aware cancellation (min_quota per workflow)
  • budget.sh workflow_min_quota() — pre-flight helper for self-skipping
  • DOCS/quota-costs.md — rendered documentation in mdBook

Phase 1 values are code-audit estimates (basis: code-audit). Phase 2 (update-quota-costs.yml, weekly) replaces them with observed p50/p95 values (basis: observed) once ≥5 run samples exist per workflow.

When adding a new workflow that makes significant REST calls, add it to config/workflow-quota-costs.yml with estimated min_quota, cost_low, cost_mid, cost_high, and basis: code-audit. Wire qi_begin/qi_end from scripts/includes/quota-instrument.sh into its main job step so Phase 2 can measure it automatically.

Instrumented workflows (Phase 2 active):

  • Sync All Forks
  • Inject Built-with-Ona Badges
  • Reconcile Org References
  • Cleanup Stale Branches
  • Check OSP-Bound CI Status
  • Sync Registered Imports
  • Mirror Interested-Deving-1896 → OSP
  • Pre-Mirror CI Gate
  • Verify Mirror Integrity
  • Post-Flush Verification
  • Pipeline Telemetry
  • Translate Docs

Path filters + required status checks (gate job pattern)

When a workflow uses path filters to skip jobs on irrelevant changes, required status checks will block PRs indefinitely if the filtered jobs never run. Fix this with a gate job that always runs and reflects the filtered outcomes:

jobs:
  changes:
    name: Detect changes
    runs-on: ubuntu-latest
    outputs:
      shell: ${{ steps.filter.outputs.shell }}
    steps:
      - uses: actions/checkout@v4
      - uses: dorny/paths-filter@v3
        id: filter
        with:
          filters: |
            shell:
              - '**/*.sh'

  lint:
    name: ShellCheck
    needs: changes
    if: needs.changes.outputs.shell == 'true'
    runs-on: ubuntu-latest
    steps: [...]

  # Set THIS as the required status check — not the individual jobs above.
  ci-required:
    name: CI Required
    runs-on: ubuntu-latest
    needs: [lint]
    if: always()
    steps:
      - name: Check results
        run: |
          if echo "${{ join(needs.*.result, ' ') }}" | grep -qw "failure"; then
            exit 1
          fi

Branch protection must require CI Required (the gate job name), not the individual filtered job names. If the individual names are listed as required checks, PRs that skip those jobs will be permanently blocked.

Applied in: btrfs-dwarfs-framework/.github/workflows/ci.yaml


Template sync profiles

config/template-consumers.yml controls which repos receive automatic file updates from sync-template.yml. Each consumer has a profile that determines what gets injected.

Profile assignments

ProfileWhat it injectsWho should use it
fullEverything — all workflows, scripts, configfork-sync-all only
mirrorMirror/sync workflows + infra toolingNobody — deprecated, do not assign
infra-corePR automation, token rotation, token health, README render validationConsumer repos that are targets of the mirror chain
standalonePR automation + token rotation onlyExternal project forks (KDE Invent, etc.)
upstream-syncUpstream sync workflowsRepos that track upstream projects

Critical rule

Never assign mirror profile to consumer repos. The mirror profile injects the full fork-sync-all mirror/sync suite (60+ workflow files, 100+ scripts) into repos that are targets of the mirror chain, not operators of it. This causes template pollution — files that have no purpose in the target repo and clutter its .github/workflows/ and scripts/ directories.

Template pollution cleanup

If a repo has been polluted by the mirror profile:

  1. Check which files don't belong:
for f in .github/workflows/*.yml; do
  grep -q "SYNC_TOKEN\|openos-project\|mirror-to-osp\|registered-imports" "$f" \
    && echo "POLLUTION: $(basename $f)" \
    || echo "native:    $(basename $f)"
done
  1. Remove them with git rm --cached and commit:
git rm --cached .github/workflows/add-mirror-repo.yml  # etc.
git commit -m "chore: remove fork-sync-all template pollution"
  1. Delete the untracked files from disk:
git status --short | grep "^??" | awk '{print $2}' | xargs rm -f
  1. Trigger cleanup-pollution.yml (workflow_dispatch) to clean remaining consumer repos automatically.

Repos cleaned of mirror pollution (2026-06-06)

  • KPort — 74 files removed
  • btrfs-dwarfs-framework — 133 files removed
  • All other infra-core consumers — cleaned via cleanup-pollution.yml

Queue pile-up pattern

Workflows that trigger on .github/workflows/** (e.g. validate-config, update-workflow-triggers-doc) must have concurrency: cancel-in-progress: true to prevent stacking. Without it, rapid pushes create a queue of identical runs that consume quota on every reset, causing a deadlock where the queue can't drain because quota is always 0.

concurrency:
  group: workflow-name-${{ github.ref }}
  cancel-in-progress: true

OSP-bound repo list

The canonical list of ~49 repos that are mirrored to GitLab lives in config/gitlab-subgroups.yml. Parse it with yaml.safe_load — do not hardcode repo names anywhere else.

To get the list in bash:

python3 -c "
import yaml
data = yaml.safe_load(open('config/gitlab-subgroups.yml'))
for sg in data.get('subgroups', {}).values():
    for repo in (sg.get('repos') or []):
        print(repo)
"

GitLab subgroup IDs

Parent group: openos-project on GitLab (gitlab.com/openos-project)

Subgroup slugGitLab ID
git-management_deving130516820
penguins-eggs_deving130516402
immutable-filesystem_deving130516465
linux-kernel_filesystem_deving130516188
incus_deving130516536
taubyte_deving133909500
neon-deving130739746
ops130734009
yaml-tooling_deving133909501
cachyos_deving133909503
ai-agents_deving133909504
rust-systems_deving133954601

All IDs are authoritative — sourced from config/gitlab-subgroups.yml. Do not hardcode them elsewhere.


README management

AI marker format

<!-- AI:start:section-name -->
content
<!-- AI:end:section-name -->

Eight AI-owned sections: what-it-does, architecture, ci, mirror-chain, contributors, origins, resources, license.

Human-owned sections (Install, Usage, Configuration, License) never get AI markers — they get placeholder HTML comments on first creation.

Three modes in update-readmes.sh

  • rewrite — no AI markers present → build full template from scratch
  • fill — some markers present but missing sections → inject missing ones
  • update — all markers present → regenerate AI section content

check-readme-render.sh

Run this against any README before committing. It catches: leaked log lines, unclosed fences, unclosed AI markers, empty sections, missing H1, broken tables, bare [text] links, raw angle brackets.

bash scripts/check-readme-render.sh path/to/README.md

GitLab CI variables

These must be set as masked CI/CD variables in the openos-project/fork-sync-all GitLab project settings (not GitHub secrets):

VariableMaps toUsed byNotes
GITLAB_TOKENGITLAB_TOKEN GitHub secretMost GitLab CI jobsapi + read_repository + write_repository scope
WORKFLOW_SECRETSYNC_TOKEN GitHub secretsync-forks, notify-poller, resolve-failures, rate-limit-rerun, token-health, cleanup-branchesGitHub PAT with repo + workflow + admin:org scopes
GH_SYNC_TOKENGH_SYNC_TOKEN GitHub secretsync-from-gitlabGitHub PAT with repo + workflow scopes
GITLAB_MAINTENANCE_TOKENmaintain:storageInherited from openos-project group variable; api scope on GitLab

Headroom proxy

A context compression proxy runs on port 8787 (started automatically via .ona/automations.yaml). To use it with Claude:

ANTHROPIC_BASE_URL=http://localhost:8787 claude
# or
headroom wrap claude

Check savings: headroom stats


Token rotation

Tracked tokens

The "PAT name" column is the display name shown at github.com/settings/tokens (classic).

SecretPAT nameScopePlatform / OrgExpiryUsed byRotate via
SYNC_TOKENfork-sync-all SYNC_TOKENadmin:org, admin:org_hook, admin:repo_hook, audit_log, delete:packages, delete_repo, gist, notifications, project, repo, workflow, write:packagesGitHub / I-D-18962026-09-02Most workflowsrotate-token.yml
GH_SYNC_TOKENsync-mirror-watchdogadmin:org, admin:org_hook, admin:public_key, admin:repo_hook, audit_log, gist, notifications, project, repo, workflow, write:discussion, write:packagesGitHub / I-D-18962026-09-03mirror workflowsrotate-token.yml
OSP_ADMIN_TOKENOSP_ADMIN_TOKENadmin:orgGitHub / OpenOS-Project-OSP2026-09-03rotate-token.yml (OSP org secret rotation)rotate-token.yml
MIRROR_TOKENOSP-ORG Mirror Tokenadmin:enterprise, admin:gpg_key, admin:org, admin:org_hook, admin:public_key, admin:repo_hook, admin:ssh_signing_key, project, repo, workflowGitHub / OpenOS-Project-OSP2026-09-01mirror workflowsrotate-token.yml
ORG_MIRROR_OSP_TO_OOCOSP-ORG Mirror Token(same PAT as MIRROR_TOKEN)GitHub / OpenOS-Project-OSP2026-09-01mirror-osp-to-ooc.yamlrotate-token.yml
ADD_MIRROR_REPO_SYNCfork-sync-all-onaadmin:repo_hook, read:org, repo, workflowGitHub / I-D-18962026-08-13 ⚠️add-mirror-repo.ymlrotate-token.yml
GITLAB_SYNC_TOKENfork-sync-all-syncapi, read_repository, write_repositoryGitLab / openos-project2027-05-13sync-to-gitlab.yml, mirror-osp-to-gitlab.yml, sync-from-gitlab.ymlrotate-token.yml
GITLAB_TOKENOna-Env-SecretapiGitLab / openos-project2027-05-17Ona dev environment (injected as GITLAB_TOKEN env var); also used by gl-storage-scan, sync-to-gitlab-variant, cleanup-pollution, reconcile-org-refsrotate-token.yml
BITBUCKET_TOKENn/a (opt-in)Bitbucket APIBitbucketunknownsync-registered-imports.yml, clone-org.yml, import-repo.yml — skipped if unsetrotate-token.yml
GITEA_TOKENn/a (opt-in)Gitea APIGitea instanceunknownsync-registered-imports.yml, clone-org.yml, import-repo.yml — skipped if unsetrotate-token.yml

| ACTIVITYSMITH_API_KEY | n/a (external service) | ActivitySmith API | ActivitySmith | unknown | full-chain-flush.yml (live activity tracking) — optional, skipped if unset | manual | | ACTIVITYSMITH_CHANNELS | n/a (external service) | ActivitySmith channel IDs | ActivitySmith | n/a | full-chain-flush.yml — optional, skipped if unset | manual | | ANTHROPIC_API_KEY | n/a (external service) | Anthropic API | Anthropic | n/a | validate-config.yml (AgentShield scan) — optional, skipped if unset | manual |

How to rotate a repo secret (SYNC_TOKEN, GH_SYNC_TOKEN, etc.)

  1. Generate a new PAT at https://github.com/settings/tokens
  2. Go to rotate-token.ymlRun workflow
  3. Select the secret name from the dropdown
  4. Paste the new token value into the token_value field
  5. Leave validate checked — it confirms the token works before finishing
  6. After the run completes, update the expiry date in this table

How to rotate an OSP org secret (ORG_MIRROR_OSP_TO_OOC, MIRROR_TOKEN)

OSP org secrets live in OpenOS-Project-OSP and require a token with admin:org on that org. SYNC_TOKEN only covers Interested-Deving-1896.

The rotate-token.yml workflow resolves the OSP token automatically in this priority order:

Option 1 — GitHub App (preferred, permanent)

A GitHub App installation token never expires and has fine-grained permissions.

One-time setup:

  1. Create a GitHub App at https://github.com/settings/apps/new
    • Name: fork-sync-all-osp-rotator (or similar)
    • Permissions: Organization secrets → Read and write
    • Uncheck everything else
  2. Install the App on OpenOS-Project-OSP org
  3. Note the App ID (shown on the app settings page)
  4. Generate a private key (PEM format) from the app settings page
  5. Add two repo secrets to Interested-Deving-1896/fork-sync-all:
    • OSP_APP_ID — the numeric App ID
    • OSP_APP_PRIVATE_KEY — the full PEM contents (including header/footer)
  6. Run rotate-token.yml — it will use the App automatically

Option 2 — Dedicated PAT (bridge until App is set up)

  1. Generate a new PAT at https://github.com/settings/tokens with:
    • admin:org scope
    • Authorized for OpenOS-Project-OSP org (SSO authorize if required)
  2. Add it as repo secret OSP_ADMIN_TOKEN in Interested-Deving-1896/fork-sync-all
  3. Run rotate-token.yml — it will use OSP_ADMIN_TOKEN automatically

Option 3 — Manual fallback

If neither OSP_APP_* nor OSP_ADMIN_TOKEN is set, the workflow prints the exact error and the two options above. You can also update manually:

  1. Generate a new PAT with admin:org on OpenOS-Project-OSP
  2. Go to OSP org secrets and update the secret value directly
  3. Update the expiry date in scripts/token-monitor.sh (OSP_ORG_SECRETS array) and in the table above

⚠️ Upcoming rotations (as of 2026-06-08):

  • ADD_MIRROR_REPO_SYNC — expires 2026-08-13 (66 days). token-health.yml will open an issue around 2026-06-29.
  • MIRROR_TOKEN / ORG_MIRROR_OSP_TO_OOC — expire 2026-09-01 (85 days). Alert ~2026-07-17.
  • SYNC_TOKEN — expires 2026-09-02 (86 days). Alert ~2026-07-18.
  • GH_SYNC_TOKEN / OSP_ADMIN_TOKEN — expire 2026-09-03 (87 days). Alert ~2026-07-19.

Automated monitoring

token-health.yml runs weekly (Monday 09:00 UTC) and warns at 45 days before expiry. When a token needs attention it opens a GitHub issue labelled token-monitor. Run it manually at any time to get a current status report.


vendor/ conventions

Agnostic-by-default rule

Everything imported into vendor/ must be deployment-agnostic. No distro names, org-specific URLs, org/repo slugs, or arch/repo paths may appear as hardcoded fallback values in shell ${VAR:-...}, YAML || '...', or TypeScript ?? '...' expressions. All deployment-identity values belong in CI variables or repo vars set per deployment.

Enforcement

scripts/check-vendor-agnostic.sh scans a vendor directory and exits 1 on violations:

bash scripts/check-vendor-agnostic.sh vendor/infra-dashboard   # specific component
bash scripts/check-vendor-agnostic.sh vendor                   # all of vendor/

enforce-agnostic-vendor.yml runs this automatically on every push/PR touching vendor/.

To suppress a specific line that is intentionally non-agnostic:

SOME_VAR="${SOME_VAR:-specific-value}"  # check-vendor-agnostic: ignore

What the checker flags vs. allows

Flagged (deployment-identity):

  • Public URLs as fallbacks: ${VITE_ENDPOINT_URL:-https://api.myorg.com}
  • Org/repo slugs: ${MIRRORLIST_REPO:-MyOrg/my-repo}
  • Arch/repo paths: ${MIRROR_REPO_PATHS:-x86_64/core,x86_64/extra}
  • Bare distro names: ${DISTRO:-cachyos}, ${DISTRO:-ubuntu}

Allowed (generic defaults):

  • Localhost dev URLs: ${API_URL:-http://localhost:5862}
  • Generic relative paths: ${MIRRORLIST_PATH:-mirrorlist/mirrorlist}
  • Single-word tokens: ${LOG_LEVEL:-info}, ${ENV:-production}
  • UI strings: ${APP_NAME:-Infra Dashboard}

Workflow integrations

import-repo → immediate sync

When ongoing_sync=true, import-repo.sh writes to registered-imports.json and then immediately dispatches sync-registered-imports.yml with repo_filter=<name> and force_sync=true. This avoids the up-to-6h wait for the scheduled run to pick up the new entry.

If the dispatch fails (quota, permissions), it falls back gracefully — the entry is still registered and will sync on the next scheduled run.

merge-to-monorepo → OSP mirror chain

merge-to-monorepo.yml has a mirror_monorepo boolean input (default: false). When set, it dispatches add-mirror-repo.yml for the newly created monorepo after a successful merge, entering it into the standard OSP mirror chain automatically.


Known pitfalls

  • fill_missing_sections case statement — must handle all 8 AI sections. If you add a new section to ALL_AI_SECTIONS, add it to the case in fill_missing_sections, rewrite_readme, and the update mode loop.

  • sync-registered-imports.sh does not create reposensure_gh_repo() handles creation now, but the target repo must be reachable via the GitHub API. New entries in registered-imports.json will auto-create the repo on first run.

  • GitLab mirror chainI-D-1896 → OpenOS-Project-OSP (GitHub) → openos-project (GitLab). Adding a repo to gitlab-subgroups.yml is required for GitLab mirroring. Adding to registered-imports.json is required for upstream sync. Both are independent — a repo can be in one without the other.

  • _inter_repo_sleep in update-readmes.sh — quota-aware pacing. No delay when quota > 2000; scales to 30s when < 500. The cached _quota_remaining variable is decremented by 10 per repo to trigger re-checks before actually hitting the threshold.

Architecture

The three-org chain

fork-sync-all is the control plane for a three-organisation mirror chain on GitHub, with a fourth leg into GitLab:

Interested-Deving-1896  ──►  OpenOS-Project-OSP  ──►  OpenOS-Project-Ecosystem-OOC
        ▲                                                         │
        └─────────── upstream-commits / upstream-prs ────────────┘
                                    │
                                    ▼
                         GitLab openos-project
                    (12 subgroups, ~157 repos mirrored)
OrgRole
Interested-Deving-1896Primary — forks live here, all automation runs here
OpenOS-Project-OSPSecondary mirror — receives pushes from I-D-1896
OpenOS-Project-Ecosystem-OOCTertiary mirror — receives pushes from OSP
gitlab.com/openos-projectGitLab mirror — receives pushes from OSP via GitLab CI

All automation runs in Interested-Deving-1896/fork-sync-all. The other orgs are passive recipients — they do not run their own automation except for the GitLab CI mirror job that pushes back to GitLab.


Data flow

Inbound (upstream → I-D-1896)

Three paths bring upstream changes into Interested-Deving-1896:

  1. sync-forks.yml (daily) — syncs all GitHub forks with their upstream parents
  2. sync-registered-imports.yml (every 6h) — re-syncs repos registered in registered-imports.json, including non-GitHub sources (GitLab, Bitbucket, Codeberg, etc.)
  3. upstream-commits.yml / upstream-prs.yml (every 6h) — detects direct commits and open PRs in OSP/OOC that haven't been reflected upstream, and opens PRs in I-D-1896

Outbound (I-D-1896 → OSP → OOC → GitLab)

The mirror chain runs in sequence, each leg triggered by the previous:

mirror-to-osp.yml  ──►  mirror-osp-to-ooc.yaml  ──►  GitLab CI (sync-to-gitlab.yml)
   (every 6h)              (every 2h at :15)           (on OSP push)

full-chain-flush.yml orchestrates a complete end-to-end run of all three legs plus README updates, sync, and validation in a single coordinated sequence.

GitLab subgroup placement

config/gitlab-subgroups.yml is the single source of truth for which repos go into which GitLab subgroup. The 12 subgroups map to topic areas:

SubgroupTopic
git-management_devingGit tooling
penguins-eggs_devingpenguins-eggs ecosystem
immutable-filesystem_devingImmutable Linux
linux-kernel_filesystem_devingKernel and filesystem
incus_devingIncus / container infrastructure
taubyte_devingTaubyte platform
neon-devingKDE Neon ecosystem
opsOperations and control plane
yaml-tooling_devingYAML, CI, and tooling
cachyos_devingCachyOS packages
ai-agents_devingAI agent tooling
rust-systems_devingRust system tools

Repos not listed in any subgroup fall into the ops default subgroup.


Quota management

Both GH_TOKEN and SYNC_TOKEN belong to the same GitHub user and share a single 5000 req/hr REST bucket. The system has three layers of protection:

quota-monitor.yml  ──►  quota-reserve.yml  ──►  queue-manager.yml
  (every 10 min)          (every 10 min)          (every 15 min)
LayerThresholdAction
quota-reserve< 1000 remainingCancels tier-4 (LOW) queued runs
quota-reserve< 500 remainingCancels tier-3 (MEDIUM) queued runs
queue-managerRun queued > 25 minEvicts stale queued runs
queue-managerDuplicate workflowKeeps newest, cancels older

Workflow priority tiers are defined in config/workflow-priority-tiers.yml. Tier 1 (CRITICAL) runs are never cancelled. See Operations for the full quota reference.


Config files

FilePurpose
config/gitlab-subgroups.ymlGitLab subgroup placement for ~157 repos
config/workflow-priority-tiers.ymlPriority tier for each workflow (used by queue-manager and quota-reserve)
config/workflow-sync.ymlGitHub ↔ GitLab CI job mapping (used by validate-workflow-guards)
config/workflow-cost-profiles.ymlEstimated API cost per workflow run
config/ota-registry.ymlRepos opted in to the OTA update system
config/ota-blocklist.ymlOrgs/namespaces excluded from OTA by default
config/template-manifest.ymlTemplate sync profiles and file ownership
config/template-consumers.ymlRepos consuming each template profile
registered-imports.jsonUpstream repos registered for ongoing sync

vendor/

vendor/ contains third-party components that fork-sync-all hosts or deploys. It is distinct from scripts/ (first-party automation) and config/ (config data).

Current components:

ComponentDescription
vendor/infra-dashboardMirror-health and package-search SPA + Rust API backend

All vendored components must be deployment-agnostic — no distro names, org-specific URLs, or hardcoded deployment values. See Contributing for the enforcement workflow.


Token architecture

Two GitHub PATs are in active use, both owned by the same user (ID 202036334) and sharing the same 5000 req/hr quota:

SecretUsed byScope
SYNC_TOKENMost workflowsrepo, workflow, admin:org
GH_TOKENValidation, README, config workflowsrepo, workflow

GitLab operations use GITLAB_SYNC_TOKEN (api, read/write_repository scope).

Token expiry is monitored weekly by token-health.yml. See Token Rotation for rotation procedures.

Workflow Reference

All workflows in .github/workflows/, grouped by priority tier. For trigger details and schedules see Workflow Triggers.

Auto-generated on 2026-06-09 from config/workflow-quota-costs.yml and config/workflow-priority-tiers.yml.

Quota cost columns: Low = fast/cached run · Mid = typical (p50) · High = large/uncached (p95)


Tier 1 — Critical

WorkflowSynopsisSchedulemin_quotaLowMidHigh
Cancel Runs After Token RotationCancels queued and in-progress runs that hold the old (now-invalid) token immediately after Rotate Secret Token completes, preventing Bad credentials failures.Manual5052050
Cancel Stale RunsCancels queued and in-progress workflow runs older than MAX_AGE_MINUTES (default 90) or created before a fix commit, preventing stale runs from burning quota.Manual100103080
Critical DeployFast-lane workflow for deploying critical fixes when the system is degraded — commits and pushes changes, clears the queue aggressively, then dispatches priority workflows.Manual50530100
Mirror WatchdogTriggers when any mirror workflow fails — waits 5 minutes then retries once. Surfaces persistent failures in the Actions tab without consuming quota on repeated retries.5051530
Pre-Flush PrepPrepares the system for a clean full-chain-flush — cancels stale runs, merges ready PRs, validates config, cleans merged branches, removes template pollution, then dispatches full-chain-flush when quota is sufficient.Manual100103060
Queue ManagerDeduplicates queued workflow runs (keeps newest per workflow) and evicts runs queued longer than STALE_QUEUE_MIN (default 25 min) to prevent quota exhaustion cascades.Every 30 min5051530
Quota MonitorPolls GitHub quota and optionally dispatches a target workflow once quota recovers above a configurable threshold. Dispatch-only — never scheduled.101510
Quota ReserveCancels low-priority queued runs when remaining quota drops below RESERVE_FLOOR (default 1000). Uses per-workflow min_quota from workflow-quota-costs.yml for cost-aware cancellation.Every 30 min101515
Rate-Limit Re-triggerScans recently-failed workflow runs, identifies those that failed due to rate limiting, and re-triggers them after their quota reset epoch.Every 4h at :055052050
Rotate Secret TokenRotates GitHub PATs and GitLab tokens stored as org/repo secrets. Validates the new token before committing, then triggers Cancel Runs After Token Rotation to clear stale runs.Manual5051020
Token Health MonitorChecks expiry dates for all tracked PATs and GitLab tokens. Opens a GitHub issue labelled token-monitor when any token expires within 45 days.Weekly Mon 09:24 UTC5051020
Validate ConfigValidates all config files (gitlab-subgroups.yml, workflow-sync.yml, priority-tiers.yml, registered-imports.json) on every push that touches them. Blocks merges on invalid config.Manual502510

Tier 2 — High

WorkflowSynopsisSchedulemin_quotaLowMidHigh
Add Mirror RepoAdds a new repo to the three-org mirror chain (Interested-Deving-1896 → OSP → OOC) by creating the repo in each org, setting up webhooks, and registering it in gitlab-subgroups.yml.Manual200103060
Full Chain FlushOrchestrates the complete mirror chain in sequence — mirror-to-osp → mirror-osp-to-ooc → mirror-osp-to-gitlab — with quota checks between each stage.17 5 1 * *10001004001000
Import RepositoryPlatform-agnostic repo importer — clones any public or authenticated git URL into Interested-Deving-1896, optionally mirrors through the OSP→OOC chain and registers for ongoing sync.100103060
Merge Repos into MonorepoMerges multiple git repositories into a single monorepo, preserving full commit history, tags, and Git LFS objects. Manual dispatch only.Manual100103060
Mirror Interested-Deving-1896 → OSPBare-clones every repo in Interested-Deving-1896 and git push --mirror into OpenOS-Project-OSP, syncing all branches, tags, and refs exactly.Every 6h at :135002080200
Mirror OSP → GitLabMirrors every repo in OpenOS-Project-OSP to its GitLab counterpart under openos-project, creating the GitLab project in the correct subgroup if it does not exist yet.Every 8h at :2330052050
Mirror to OpenOS-Project-Ecosystem-OOCBare-clones every repo in OpenOS-Project-OSP and git push --mirror into OpenOS-Project-Ecosystem-OOC, completing the second hop of the three-org mirror chain.Every 6h at :153002080200
Pre-Mirror CI GateChecks CI status on all OSP-bound repos in Interested-Deving-1896 before mirroring. Dispatches resolve-failures for red repos, waits, then re-checks. Blocks the mirror if repos are still failing.Manual80050150300
Rebase PRsRebases open PRs in Interested-Deving-1896 onto their base branch when they fall behind, keeping PRs mergeable without manual intervention.Daily 05:10 UTC10052050
Sync All ForksSyncs all branches of every fork owned by Interested-Deving-1896 with their upstream via the GitHub merge-upstream API, falling back to force-reset on divergence.Daily 06:07 UTC50050200500
Sync Registered ImportsRe-syncs all repos listed in registered-imports.json — bare-clones each source URL and pushes all branches and tags to Interested-Deving-1896.Every 6h at :5520051530
Sync from GitLabPulls changes from the GitLab mirror back to GitHub when GitLab CI has committed changes (e.g. generated files, CI artifacts).Daily 04:22 UTC10052050
Sync to GitLab VariantVariant of Sync to GitLab that uses a different token and push strategy — used when the primary sync is blocked or for testing.Every 8h at :5010052050

Tier 3 — Medium

WorkflowSynopsisSchedulemin_quotaLowMidHigh
Check OSP-Bound CI StatusChecks CI status across all OSP-bound repos and reports failing workflows. Triggers resolve-failures when failures are detected.Daily 09:05 UTC30050150300
Cleanup Stale BranchesDeletes branches that have been merged into the default branch across all repos in Interested-Deving-1896, OSP, and OOC.29 4 1 * *2001060200
Cleanup Template PollutionRemoves files incorrectly propagated from fork-sync-all to consumer repos via the template sync pipeline, across all three GitHub orgs and GitLab.Manual2002080200
Clone OrgClones all repositories from an org or user on any supported platform (GitHub, GitLab, Bitbucket, Gitea) into Interested-Deving-1896.Manual2002080200
Create Missing READMEsCreates README.md from the standard template for OSP-bound repos that have no README, with placeholder sections for human-owned content.Daily 07:08 UTC2002080200
Deploy BookBuilds the mdBook documentation site from DOCS/ and deploys it to GitHub Pages at interested-deving-1896.github.io/fork-sync-all/.Manual5051020
Docker → Incus MigrationScans repos for Docker artifacts (Dockerfile, docker-compose.yml) and replaces them with Incus equivalents. Runs after Add Mirror Repo and weekly.Weekly Sun 03:08 UTC1001040100
Enforce Agnostic VendorScans vendor/ for distro-specific hardcoded fallback values in shell, YAML, and TypeScript. All vendored components must be deployment-agnostic.Manual502510
Fork KDE Neon ReposOne-shot workflow that clones the 6 KDE Invent neon repos into Interested-Deving-1896 and pushes them through the OSP mirror chain. Ongoing re-sync handled by sync-registered-imports.Manual100103060
Generate Book PagesRegenerates DOCS/generated/ pages from config sources (workflow-quota-costs.yml, priority-tiers.yml, gitlab-subgroups.yml, registered-imports.json) and commits the result.Manual50125
Inject Built-with-Ona BadgesAdds a Built-with-Ona badge to README.md for all repos in Interested-Deving-1896 that are missing it. Skips repos that already have the badge.Daily 08:15 UTC20053080
OTA DiscoverScans forks of fork-sync-all for .ota/config.yml with enabled: true and adds newly discovered repos to config/ota-registry.yml.Daily 06:38 UTC1001040100
OTA Opt-InPropagated to opted-in forks. Fork owners run this once to create .ota/config.yml and open a registration PR against fork-sync-all's OTA registry.5051530
OTA ReleaseTriggered on semver tag push. Assembles and delivers OTA updates to all opted-in repos in config/ota-registry.yml, then updates CHANGELOG.md with release notes.Manual1001040100
OTA Self-UpdatePropagated to opted-in forks. Pulls the latest OTA release from fork-sync-all and applies it to the fork's workflow files.Weekly Mon 05:15 UTC5051530
PR AutomationApplies size labels, path-based labels, reviewer auto-assignment, risky pattern detection, and auto-merge for low-risk PRs on every PR open or update.On push5051530
Pipeline TelemetryPost-run observability workflow. Fetches completed run data, builds a span tree (workflow→jobs→steps), computes Thoth-equivalent metrics, parses log severity, writes a step summary and trace artifact, and upserts a rolling metrics issue.Manual20051530
Post-Flush VerificationEnd-to-end health check after full-chain-flush — mirror integrity across all three pairs, CI status on I-D-1896 OSP-bound repos, quota health, and workflow queue health.Manual300150350600
Rebuild LTS Branch (penguins-eggs)Rebases the all-features branch onto the upstream master after each pieroproietti sync, then force-pushes the result to the lts branch.Manual5051530
Reconcile Org ReferencesRewrites org/repo references in OSP and OOC mirrors to point at the correct org, fixing stale Interested-Deving-1896 references left by the mirror process.Daily 05:50 UTC3001060150
Resolve CI FailuresAnalyses CI failure patterns across OSP-bound repos and applies automated fixes (dependency updates, config corrections, workflow patches) where possible.Daily 07:43 UTC1001040100
Setup Dashboard VariablesSets all VITE_* repository variables required by the infra-dashboard public-dashboard build. Safe to re-run — blank inputs leave existing variables unchanged.Manual5051530
Setup OSP Mirror WorkflowsEnsures all repos in OpenOS-Project-OSP have the correct mirror workflow files and secrets configured for the OSP→OOC mirror chain.Every 6h at :452002080200
Sync Registry SourcesRegistry-driven upstream sync — reads a JSON registry of upstream sources and syncs each repo via merge-upstream or force-reset. Agnostic generalisation of sync-pieroproietti-forks.Daily 03:05 UTC1001040100
Sync TemplateSyncs fork-sync-all's file tree into target repos. Three modes — create (new repo + mirror chain), inject (copy into existing repo), propagate (push-triggered sync to all consumers in template-consumers.yml).Manual2002080200
Sync Upstream SourcesReads the Origins section of every OSP-bound repo and syncs each referenced external fork to its upstream HEAD via merge-upstream or force-reset.Daily 01:37 UTC2002080200
Sync btrfs-devel BranchesSyncs tracked btrfs-devel branches from the upstream kernel tree into the btrfs-dwarfs-framework fork.Every 6h at :0210052050
Sync pieroproietti ForksSyncs all penguins-eggs forks owned by Interested-Deving-1896 with their upstream pieroproietti sources via merge-upstream.Every 8h at :071001040100
Sync to GitLabPushes the Interested-Deving-1896/fork-sync-all repo to its GitLab mirror at openos-project/ops/fork-sync-all.Daily 09:17 UTC10052050
Update READMEsRegenerates AI-owned sections (what-it-does, architecture, ci, mirror-chain, etc.) in README.md for all OSP-bound repos, preserving human-owned sections.Daily 03:15 UTC30050150300
Validate README RenderChecks README.md for rendering issues — leaked log lines, unclosed fences, bare brackets, raw angle brackets, unclosed AI markers, missing H1, and empty sections.Manual5051530
Verify Mirror IntegrityCompares default-branch HEAD SHAs between source and destination for all OSP-bound repos after a mirror stage. Reports mismatches as warnings; configurable hard-fail mode.Manual40050100150

Tier 4 — Low

WorkflowSynopsisSchedulemin_quotaLowMidHigh
Check GitLab CI SyncCompares paired jobs in .gitlab-ci.yml against config/workflow-sync.yml and reports drift — scripts with changed entry points, mismatched cadence rules, or jobs missing from either side.Manual502510
Generate NotebookLM ContentGenerates NotebookLM content artifacts (audio, video, slides, infographic, quiz, flashcards, report) for a given notebook and uploads them to a GitHub Release.Manual5051530
Generate OSP Dependency GraphScans all OSP-bound repos for package.json and requirements.txt files and generates a dependency graph showing inter-repo relationships.Weekly Sun 03:10 UTC1002060150
GitLab Storage ScanScans all projects under openos-project on GitLab and reports storage usage. Useful for diagnosing when the namespace approaches its 10 GiB limit.Manual502510
LTS README StandardisationStandardises README.md structure for LTS-tagged repos, ensuring they follow the LTS template with correct version badges and support tables.19 3 1 * *1001040100
List Chromium GitLab ReposLists all projects under the Chromium_Browser_OS_Deving GitLab group. Informational only — used to audit what has been mirrored.Manual502510
Mirror ArtifactsMirrors GitHub Releases, Flatpak packages, and RPM packages from Interested-Deving-1896 repos to their OSP and OOC counterparts.Every 8h at :102001050150
Mirror OrgsMirrors all repos from Interested-Deving-1896 to OpenOS-Project-OSP and OpenOS-Project-Ecosystem-OOC using bare clone + push --mirror.Daily 02:17 UTC1002060150
Mirror ReleasesMirrors GitHub Releases (tags + release notes + assets) from Interested-Deving-1896 repos to their OSP and OOC counterparts.Every 6h at :032001050150
Notification PollerPolls GitHub notifications for unread CI failure notifications and triggers resolve-failures immediately when any are found.Every 4h at :32501515
README WizardAI-guided README authoring — writes or rewrites a README for a specific repo according to custom instructions (audience, tone, sections), respecting existing human-owned markers.Manual100103060
Rate Limit StatusQueries current rate limit status for GitHub REST, GitLab, and GitHub Models APIs. Exits non-zero if any platform is below 10% remaining.Manual102510
Refresh NotebookLM AuthRotates the short-lived __Secure-1PSIDTS cookie in NOTEBOOKLM_AUTH_JSON weekly and writes the updated state back to the repo secret.Weekly Tue 06:17 UTC10125
Repo ManifestExports a manifest of all repos in an org, or imports repos from a manifest into a target GitHub org. Supports multi-platform bulk import.Manual1001040100
Setup GitLab CI SchedulesReplaces all existing GitLab pipeline schedules in openos-project/ops/fork-sync-all with the 3 consolidated CADENCE-based schedules. Safe to re-run.502510
Shallow Reclone Large GitLab MirrorsReduces GitLab storage usage by replacing full git history on large mirror projects with a shallow clone. Run when openos-project approaches its 10 GiB storage limit.Manual502510
Sync penguins-eggs docs to penguins-eggs-bookTriggered by repository_dispatch from penguins-eggs when docs/chromiumos/ changes on all-features. Syncs the updated docs into the penguins-eggs-book repo.Manual5051530
Translate DocsTranslates DOCS/ mdBook pages into a target language using GitHub Models API. Writes translated files to DOCS// and upserts a language section in SUMMARY.md.Daily 11:15 UTC10051530
Translate READMEsTranslates README.md files for OSP-bound repos into additional languages using GitHub Models API. Writes translated files alongside the English original.Daily 10:43 UTC1001040100
Trigger Artifact MirrorDispatches mirror-artifacts immediately when a release is published in this repo, so OSP and OOC receive the release without waiting for the next scheduled run.502510
Update Infrastructure DependenciesRuns Dependabot-style updates for GitHub Actions versions across all workflow files and opens a grouped PR.Weekly Mon 06:11 UTC5051530
Update Quota Cost RegistryReads quota-instrument records from job logs, computes observed p50/p95 REST costs per workflow, and commits updated values to workflow-quota-costs.yml weekly.Weekly Mon 08:00 UTC2003080150
Update Workflow Triggers DocRegenerates docs/workflow-triggers.md and docs/workflow-triggers.txt whenever a workflow file changes on main. Commits the result directly to main.Manual5051020
Upstream Direct Commits from OSP + OOCDetects commits pushed directly to OSP/OOC default branches (without a PR) and opens PRs against Interested-Deving-1896 to reconcile them.Every 6h at :472002080200
Upstream PRs from OSP + OOCDetects commits on OSP and OOC default branches that are not present in Interested-Deving-1896 and opens PRs to bring them upstream.Every 6h at :332002080200
Upstream Workflow ProposalScans OSP-bound repos for new workflow patterns not present in fork-sync-all and opens a PR proposing them as template skeletons.Weekly Mon 06:06 UTC5052050

✦ Mid value is an observed p50 measurement. All other values are code-audit estimates.

Workflow Triggers

All workflows in .github/workflows/. Grouped by function, with every trigger listed.

Plain-text version: docs/workflow-triggers.txt
Auto-generated on 2026-06-09 from .github/workflows/ and config/workflow-quota-costs.yml


Mirror Chain

WorkflowSynopsisFileScheduleAlso triggers on
Mirror Artifacts ▶ RunMirrors GitHub Releases, Flatpak packages, and RPM packages from Interested-Deving-1896 repos to their OSP and OOC counterparts.mirror-artifacts.ymlEvery 8h at :10dispatch
Mirror Orgs ▶ RunMirrors all repos from Interested-Deving-1896 to OpenOS-Project-OSP and OpenOS-Project-Ecosystem-OOC using bare clone + push --mirror.mirror-orgs-full.ymlDaily 02:17dispatch
Mirror Watchdog ▶ RunTriggers when any mirror workflow fails — waits 5 minutes then retries once. Surfaces persistent failures in the Actions tab without consuming quota on repeated retries.mirror-orgs-watchdog.ymlMirror Interested-Deving-1896 → OSP completes · Mirror Orgs completes · Mirror OSP → GitLab completes · Mirror Releases completes · Mirror Artifacts completes
Mirror OSP → GitLab ▶ RunMirrors every repo in OpenOS-Project-OSP to its GitLab counterpart under openos-project, creating the GitLab project in the correct subgroup if it does not exist yet.mirror-osp-to-gitlab.ymlEvery 8h at :23Add Mirror Repo completes · dispatch
Mirror to OpenOS-Project-Ecosystem-OOC ▶ RunBare-clones every repo in OpenOS-Project-OSP and git push --mirror into OpenOS-Project-Ecosystem-OOC, completing the second hop of the three-org mirror chain.mirror-osp-to-ooc.yamlEvery 6h at :15dispatch
Mirror Releases ▶ RunMirrors GitHub Releases (tags + release notes + assets) from Interested-Deving-1896 repos to their OSP and OOC counterparts.mirror-releases.ymlEvery 6h at :03dispatch
Mirror Interested-Deving-1896 → OSP ▶ RunBare-clones every repo in Interested-Deving-1896 and git push --mirror into OpenOS-Project-OSP, syncing all branches, tags, and refs exactly.mirror-to-osp.ymlEvery 6h at :13dispatch

OSP-Bound Repo Management

WorkflowSynopsisFileScheduleAlso triggers on
Add Mirror Repo ▶ RunAdds a new repo to the three-org mirror chain (Interested-Deving-1896 → OSP → OOC) by creating the repo in each org, setting up webhooks, and registering it in gitlab-subgroups.yml.add-mirror-repo.ymldispatch
Check OSP-Bound CI Status ▶ RunChecks CI status across all OSP-bound repos and reports failing workflows. Triggers resolve-failures when failures are detected.check-osp-ci.ymlDaily 09:05Mirror Interested-Deving-1896 → OSP completes · Add Mirror Repo completes · dispatch
Setup OSP Mirror Workflows ▶ RunEnsures all repos in OpenOS-Project-OSP have the correct mirror workflow files and secrets configured for the OSP→OOC mirror chain.setup-osp-mirrors.ymlEvery 6h at :45dispatch

Fork & Import Sync

WorkflowSynopsisFileScheduleAlso triggers on
Import Repository ▶ RunPlatform-agnostic repo importer — clones any public or authenticated git URL into Interested-Deving-1896, optionally mirrors through the OSP→OOC chain and registers for ongoing sync.import-repo.ymldispatch
Sync btrfs-devel Branches ▶ RunSyncs tracked btrfs-devel branches from the upstream kernel tree into the btrfs-dwarfs-framework fork.sync-btrfs-devel-branches.ymlEvery 6h at :02dispatch
Sync All Forks ▶ RunSyncs all branches of every fork owned by Interested-Deving-1896 with their upstream via the GitHub merge-upstream API, falling back to force-reset on divergence.sync-forks.ymlDaily 06:07dispatch
Sync from GitLab ▶ RunPulls changes from the GitLab mirror back to GitHub when GitLab CI has committed changes (e.g. generated files, CI artifacts).sync-from-gitlab.ymlDaily 04:22dispatch
Sync pieroproietti Forks ▶ RunSyncs all penguins-eggs forks owned by Interested-Deving-1896 with their upstream pieroproietti sources via merge-upstream.sync-pieroproietti-forks.ymlEvery 8h at :07dispatch
Sync Registered Imports ▶ RunRe-syncs all repos listed in registered-imports.json — bare-clones each source URL and pushes all branches and tags to Interested-Deving-1896.sync-registered-imports.ymlEvery 6h at :55dispatch
Sync Registry Sources ▶ RunRegistry-driven upstream sync — reads a JSON registry of upstream sources and syncs each repo via merge-upstream or force-reset. Agnostic generalisation of sync-pieroproietti-forks.sync-registry-sources.ymlDaily 03:05dispatch
Sync Upstream Sources ▶ RunReads the Origins section of every OSP-bound repo and syncs each referenced external fork to its upstream HEAD via merge-upstream or force-reset.sync-upstream-sources.ymlDaily 01:37dispatch
Upstream Direct Commits from OSP + OOC ▶ RunDetects commits pushed directly to OSP/OOC default branches (without a PR) and opens PRs against Interested-Deving-1896 to reconcile them.upstream-commits.ymlEvery 6h at :47dispatch
Upstream PRs from OSP + OOC ▶ RunDetects commits on OSP and OOC default branches that are not present in Interested-Deving-1896 and opens PRs to bring them upstream.upstream-prs.ymlEvery 6h at :33dispatch

GitLab Sync

WorkflowSynopsisFileScheduleAlso triggers on
Sync to GitLab Variant ▶ RunVariant of Sync to GitLab that uses a different token and push strategy — used when the primary sync is blocked or for testing.sync-to-gitlab-variant.ymlEvery 8h at :50push to config/ota-registry.yml, config/ota-blocklist.yml, .ota/schema.yml (+2 more) · dispatch
Sync to GitLab ▶ RunPushes the Interested-Deving-1896/fork-sync-all repo to its GitLab mirror at openos-project/ops/fork-sync-all.sync-to-gitlab.ymlDaily 09:17dispatch

README Management

WorkflowSynopsisFileScheduleAlso triggers on
Create Missing READMEs ▶ RunCreates README.md from the standard template for OSP-bound repos that have no README, with placeholder sections for human-owned content.create-readmes.ymlDaily 07:08Add Mirror Repo completes · Import Repository completes · Clone Org completes · Merge Repos into Monorepo completes · dispatch
Inject Built-with-Ona Badges ▶ RunAdds a Built-with-Ona badge to README.md for all repos in Interested-Deving-1896 that are missing it. Skips repos that already have the badge.inject-badges.ymlDaily 08:15Mirror OSP → GitLab completes · dispatch
LTS README Standardisation ▶ RunStandardises README.md structure for LTS-tagged repos, ensuring they follow the LTS template with correct version badges and support tables.lts-readmes.ymlMonthly 1st 03:19Rebuild LTS Branch (penguins-eggs) completes · dispatch
README Wizard ▶ RunAI-guided README authoring — writes or rewrites a README for a specific repo according to custom instructions (audience, tone, sections), respecting existing human-owned markers.readme-wizard.ymldispatch
Translate READMEs ▶ RunTranslates README.md files for OSP-bound repos into additional languages using GitHub Models API. Writes translated files alongside the English original.translate-readmes.ymlDaily 10:43Update READMEs completes · Add Mirror Repo completes · Import Repository completes · Clone Org completes · Merge Repos into Monorepo completes · Sync All Forks completes · Sync Registered Imports completes · Sync from GitLab completes · Sync pieroproietti Forks completes · Sync Upstream Sources completes · Sync penguins-eggs docs to penguins-eggs-book completes · dispatch
Update READMEs ▶ RunRegenerates AI-owned sections (what-it-does, architecture, ci, mirror-chain, etc.) in README.md for all OSP-bound repos, preserving human-owned sections.update-readmes.ymlDaily 03:15push to config/gitlab-subgroups.yml, config/template-manifest.yml · Sync Registered Imports completes · dispatch
Validate README Render ▶ RunChecks README.md for rendering issues — leaked log lines, unclosed fences, bare brackets, raw angle brackets, unclosed AI markers, missing H1, and empty sections.validate-readme-render.ymlpush to README.md · Update READMEs completes · dispatch

CI & Failure Resolution

WorkflowSynopsisFileScheduleAlso triggers on
Check GitLab CI Sync ▶ RunCompares paired jobs in .gitlab-ci.yml against config/workflow-sync.yml and reports drift — scripts with changed entry points, mismatched cadence rules, or jobs missing from either side.check-gitlab-sync.ymldispatch
Notification Poller ▶ RunPolls GitHub notifications for unread CI failure notifications and triggers resolve-failures immediately when any are found.notify-poller.ymlEvery 4h at :32dispatch
PR Automation ▶ RunApplies size labels, path-based labels, reviewer auto-assignment, risky pattern detection, and auto-merge for low-risk PRs on every PR open or update.pr-automation.ymlpull_request
Rate-Limit Re-trigger ▶ RunScans recently-failed workflow runs, identifies those that failed due to rate limiting, and re-triggers them after their quota reset epoch.rate-limit-rerun.ymlEvery 4h at :05dispatch
Rate Limit Status ▶ RunQueries current rate limit status for GitHub REST, GitLab, and GitHub Models APIs. Exits non-zero if any platform is below 10% remaining.rate-limit-status.ymldispatch
Rebuild LTS Branch (penguins-eggs) ▶ RunRebases the all-features branch onto the upstream master after each pieroproietti sync, then force-pushes the result to the lts branch.rebase-lts.ymlSync pieroproietti Forks completes · dispatch
Resolve CI Failures ▶ RunAnalyses CI failure patterns across OSP-bound repos and applies automated fixes (dependency updates, config corrections, workflow patches) where possible.resolve-failures.ymlDaily 07:43dispatch

Maintenance & Housekeeping

WorkflowSynopsisFileScheduleAlso triggers on
Cancel Runs After Token Rotation ▶ RunCancels queued and in-progress runs that hold the old (now-invalid) token immediately after Rotate Secret Token completes, preventing Bad credentials failures.cancel-post-rotation.ymlRotate Secret Token completes
Cleanup Stale Branches ▶ RunDeletes branches that have been merged into the default branch across all repos in Interested-Deving-1896, OSP, and OOC.cleanup-branches.ymlMonthly 1st 04:29dispatch
Cleanup Template Pollution ▶ RunRemoves files incorrectly propagated from fork-sync-all to consumer repos via the template sync pipeline, across all three GitHub orgs and GitLab.cleanup-pollution.ymldispatch
Generate OSP Dependency Graph ▶ RunScans all OSP-bound repos for package.json and requirements.txt files and generates a dependency graph showing inter-repo relationships.generate-dep-graph.ymlWeekly Sun 03:10dispatch
Reconcile Org References ▶ RunRewrites org/repo references in OSP and OOC mirrors to point at the correct org, fixing stale Interested-Deving-1896 references left by the mirror process.reconcile-org-refs.ymlDaily 05:50dispatch
Rotate Secret Token ▶ RunRotates GitHub PATs and GitLab tokens stored as org/repo secrets. Validates the new token before committing, then triggers Cancel Runs After Token Rotation to clear stale runs.rotate-token.ymldispatch
Sync Template ▶ RunSyncs fork-sync-all's file tree into target repos. Three modes — create (new repo + mirror chain), inject (copy into existing repo), propagate (push-triggered sync to all consumers in template-consumers.yml).sync-template.ymlpush to .devcontainer/**, .ona/**, config/template-manifest.yml · dispatch
Token Health Monitor ▶ RunChecks expiry dates for all tracked PATs and GitLab tokens. Opens a GitHub issue labelled token-monitor when any token expires within 45 days.token-health.ymlWeekly Mon 09:24dispatch
Update Infrastructure Dependencies ▶ RunRuns Dependabot-style updates for GitHub Actions versions across all workflow files and opens a grouped PR.update-infra-deps.ymlWeekly Mon 06:11dispatch
Update Workflow Triggers Doc ▶ RunRegenerates docs/workflow-triggers.md and docs/workflow-triggers.txt whenever a workflow file changes on main. Commits the result directly to main.update-workflow-triggers-doc.ymlpush to .github/workflows/**, config/workflow-quota-costs.yml · dispatch
Upstream Workflow Proposal ▶ RunScans OSP-bound repos for new workflow patterns not present in fork-sync-all and opens a PR proposing them as template skeletons.upstream-workflow-proposal.ymlWeekly Mon 06:06dispatch
Validate Config ▶ RunValidates all config files (gitlab-subgroups.yml, workflow-sync.yml, priority-tiers.yml, registered-imports.json) on every push that touches them. Blocks merges on invalid config.validate-config.ymlpush to config/gitlab-subgroups.yml, config/workflow-sync.yml, config/workflow-cost-profiles.yml (+16 more) · pull_request · dispatch

Full Pipeline

WorkflowSynopsisFileScheduleAlso triggers on
Critical Deploy ▶ RunFast-lane workflow for deploying critical fixes when the system is degraded — commits and pushes changes, clears the queue aggressively, then dispatches priority workflows.critical-deploy.ymldispatch
Full Chain Flush ▶ RunOrchestrates the complete mirror chain in sequence — mirror-to-osp → mirror-osp-to-ooc → mirror-osp-to-gitlab — with quota checks between each stage.full-chain-flush.ymlMonthly 1st 05:17dispatch
Pre-Flush Prep ▶ RunPrepares the system for a clean full-chain-flush — cancels stale runs, merges ready PRs, validates config, cleans merged branches, removes template pollution, then dispatches full-chain-flush when quota is sufficient.pre-flush-prep.ymldispatch

Utility / On-Demand

WorkflowSynopsisFileTrigger
Cancel Stale Runs ▶ RunCancels queued and in-progress workflow runs older than MAX_AGE_MINUTES (default 90) or created before a fix commit, preventing stale runs from burning quota.cancel-stale-runs.ymldispatch
Clone Org ▶ RunClones all repositories from an org or user on any supported platform (GitHub, GitLab, Bitbucket, Gitea) into Interested-Deving-1896.clone-org.ymldispatch
Deploy Book ▶ RunBuilds the mdBook documentation site from DOCS/ and deploys it to GitHub Pages at interested-deving-1896.github.io/fork-sync-all/.deploy-book.ymlpush to DOCS/**, book.toml, README.md (+2 more) · dispatch
Docker → Incus Migration ▶ RunScans repos for Docker artifacts (Dockerfile, docker-compose.yml) and replaces them with Incus equivalents. Runs after Add Mirror Repo and weekly.docker-to-incus.ymlAdd Mirror Repo completes · dispatch
Enforce Agnostic Vendor ▶ RunScans vendor/ for distro-specific hardcoded fallback values in shell, YAML, and TypeScript. All vendored components must be deployment-agnostic.enforce-agnostic-vendor.ymlpush to vendor/**, scripts/check-vendor-agnostic.sh, .github/workflows/enforce-agnostic-vendor.yml · pull_request · dispatch
Fork KDE Neon Repos ▶ RunOne-shot workflow that clones the 6 KDE Invent neon repos into Interested-Deving-1896 and pushes them through the OSP mirror chain. Ongoing re-sync handled by sync-registered-imports.fork-neon-repos.ymldispatch
Generate Book Pages ▶ RunRegenerates DOCS/generated/ pages from config sources (workflow-quota-costs.yml, priority-tiers.yml, gitlab-subgroups.yml, registered-imports.json) and commits the result.generate-book-pages.ymlpush to config/workflow-quota-costs.yml, config/workflow-priority-tiers.yml, config/gitlab-subgroups.yml (+2 more) · dispatch
Generate NotebookLM Content ▶ RunGenerates NotebookLM content artifacts (audio, video, slides, infographic, quiz, flashcards, report) for a given notebook and uploads them to a GitHub Release.generate-notebooklm.ymldispatch
GitLab Storage Scan ▶ RunScans all projects under openos-project on GitLab and reports storage usage. Useful for diagnosing when the namespace approaches its 10 GiB limit.gl-storage-scan.ymldispatch
List Chromium GitLab Repos ▶ RunLists all projects under the Chromium_Browser_OS_Deving GitLab group. Informational only — used to audit what has been mirrored.list-chromium-repos.ymldispatch
Merge Repos into Monorepo ▶ RunMerges multiple git repositories into a single monorepo, preserving full commit history, tags, and Git LFS objects. Manual dispatch only.merge-to-monorepo.ymldispatch
OTA Discover ▶ RunScans forks of fork-sync-all for .ota/config.yml with enabled: true and adds newly discovered repos to config/ota-registry.yml.ota-discover.ymldispatch
OTA Opt-In ▶ RunPropagated to opted-in forks. Fork owners run this once to create .ota/config.yml and open a registration PR against fork-sync-all's OTA registry.ota-opt-in.ymldispatch
OTA Release ▶ RunTriggered on semver tag push. Assembles and delivers OTA updates to all opted-in repos in config/ota-registry.yml, then updates CHANGELOG.md with release notes.ota-release.ymlpush to (any) · dispatch
OTA Self-Update ▶ RunPropagated to opted-in forks. Pulls the latest OTA release from fork-sync-all and applies it to the fork's workflow files.ota-self-update.ymldispatch
Pipeline Telemetry ▶ RunPost-run observability workflow. Fetches completed run data, builds a span tree (workflow→jobs→steps), computes Thoth-equivalent metrics, parses log severity, writes a step summary and trace artifact, and upserts a rolling metrics issue.pipeline-telemetry.ymlFull Chain Flush completes · Pre-Flush Prep completes · Mirror Interested-Deving-1896 → OSP completes · Mirror OSP → GitLab completes · Mirror to OpenOS-Project-Ecosystem-OOC completes · Reconcile Org References completes · Check OSP-Bound CI Status completes · Sync All Forks completes · Sync Registered Imports completes · Pre-Mirror CI Gate completes · Verify Mirror Integrity completes · Post-Flush Verification completes · dispatch
Post-Flush Verification ▶ RunEnd-to-end health check after full-chain-flush — mirror integrity across all three pairs, CI status on I-D-1896 OSP-bound repos, quota health, and workflow queue health.post-flush-prep.ymldispatch
Pre-Mirror CI Gate ▶ RunChecks CI status on all OSP-bound repos in Interested-Deving-1896 before mirroring. Dispatches resolve-failures for red repos, waits, then re-checks. Blocks the mirror if repos are still failing.pre-mirror-ci-gate.ymldispatch
Queue Manager ▶ RunDeduplicates queued workflow runs (keeps newest per workflow) and evicts runs queued longer than STALE_QUEUE_MIN (default 25 min) to prevent quota exhaustion cascades.queue-manager.ymldispatch
Quota Monitor ▶ RunPolls GitHub quota and optionally dispatches a target workflow once quota recovers above a configurable threshold. Dispatch-only — never scheduled.quota-monitor.ymldispatch
Quota Reserve ▶ RunCancels low-priority queued runs when remaining quota drops below RESERVE_FLOOR (default 1000). Uses per-workflow min_quota from workflow-quota-costs.yml for cost-aware cancellation.quota-reserve.ymldispatch
Rebase PRs ▶ RunRebases open PRs in Interested-Deving-1896 onto their base branch when they fall behind, keeping PRs mergeable without manual intervention.rebase-prs.ymlValidate Config completes · dispatch
Refresh NotebookLM Auth ▶ RunRotates the short-lived __Secure-1PSIDTS cookie in NOTEBOOKLM_AUTH_JSON weekly and writes the updated state back to the repo secret.refresh-notebooklm-auth.ymldispatch
Repo Manifest ▶ RunExports a manifest of all repos in an org, or imports repos from a manifest into a target GitHub org. Supports multi-platform bulk import.repo-manifest.ymldispatch
Setup Dashboard Variables ▶ RunSets all VITE_* repository variables required by the infra-dashboard public-dashboard build. Safe to re-run — blank inputs leave existing variables unchanged.setup-dashboard-vars.ymldispatch
Setup GitLab CI Schedules ▶ RunReplaces all existing GitLab pipeline schedules in openos-project/ops/fork-sync-all with the 3 consolidated CADENCE-based schedules. Safe to re-run.setup-gitlab-schedules.ymldispatch
Shallow Reclone Large GitLab Mirrors ▶ RunReduces GitLab storage usage by replacing full git history on large mirror projects with a shallow clone. Run when openos-project approaches its 10 GiB storage limit.shallow-reclone-chromium.ymldispatch
Sync penguins-eggs docs to penguins-eggs-book ▶ RunTriggered by repository_dispatch from penguins-eggs when docs/chromiumos/ changes on all-features. Syncs the updated docs into the penguins-eggs-book repo.sync-eggs-docs-to-book.ymldispatch
Translate Docs ▶ RunTranslates DOCS/ mdBook pages into a target language using GitHub Models API. Writes translated files to DOCS// and upserts a language section in SUMMARY.md.translate-docs.ymlDeploy Book completes · dispatch
Trigger Artifact Mirror ▶ RunDispatches mirror-artifacts immediately when a release is published in this repo, so OSP and OOC receive the release without waiting for the next scheduled run.trigger-artifact-mirror.yml
Update Quota Cost Registry ▶ RunReads quota-instrument records from job logs, computes observed p50/p95 REST costs per workflow, and commits updated values to workflow-quota-costs.yml weekly.update-quota-costs.ymldispatch
Verify Mirror Integrity ▶ RunCompares default-branch HEAD SHAs between source and destination for all OSP-bound repos after a mirror stage. Reports mismatches as warnings; configurable hard-fail mode.verify-mirror-integrity.ymldispatch

Schedule Summary (UTC)

TimeFrequencyWorkflow
01:37DailySync Upstream Sources ▶ Run
02:17DailyMirror Orgs ▶ Run
03:05DailySync Registry Sources ▶ Run
Sun 03:08WeeklyDocker → Incus Migration ▶ Run
Sun 03:10WeeklyGenerate OSP Dependency Graph ▶ Run
03:15DailyUpdate READMEs ▶ Run
1st 03:19MonthlyLTS README Standardisation ▶ Run
at :05Every 4hRate-Limit Re-trigger ▶ Run
04:22DailySync from GitLab ▶ Run
1st 04:29MonthlyCleanup Stale Branches ▶ Run
at :32Every 4hNotification Poller ▶ Run
05:10DailyRebase PRs ▶ Run
Mon 05:15WeeklyOTA Self-Update ▶ Run
1st 05:17MonthlyFull Chain Flush ▶ Run
05:50DailyReconcile Org References ▶ Run
at :02Every 6hSync btrfs-devel Branches ▶ Run
at :03Every 6hMirror Releases ▶ Run
Mon 06:06WeeklyUpstream Workflow Proposal ▶ Run
06:07DailySync All Forks ▶ Run
Mon 06:11WeeklyUpdate Infrastructure Dependencies ▶ Run
at :13Every 6hMirror Interested-Deving-1896 → OSP ▶ Run
at :15Every 6hMirror to OpenOS-Project-Ecosystem-OOC ▶ Run
Tue 06:17WeeklyRefresh NotebookLM Auth ▶ Run
at :33Every 6hUpstream PRs from OSP + OOC ▶ Run
06:38DailyOTA Discover ▶ Run
at :45Every 6hSetup OSP Mirror Workflows ▶ Run
at :47Every 6hUpstream Direct Commits from OSP + OOC ▶ Run
at :55Every 6hSync Registered Imports ▶ Run
07:08DailyCreate Missing READMEs ▶ Run
07:43DailyResolve CI Failures ▶ Run
Mon 08:00WeeklyUpdate Quota Cost Registry ▶ Run
at :07Every 8hSync pieroproietti Forks ▶ Run
at :10Every 8hMirror Artifacts ▶ Run
08:15DailyInject Built-with-Ona Badges ▶ Run
at :23Every 8hMirror OSP → GitLab ▶ Run
at :50Every 8hSync to GitLab Variant ▶ Run
09:05DailyCheck OSP-Bound CI Status ▶ Run
09:17DailySync to GitLab ▶ Run
Mon 09:24WeeklyToken Health Monitor ▶ Run
10:43DailyTranslate READMEs ▶ Run
11:15DailyTranslate Docs ▶ Run
Every 30 minQueue Manager ▶ Run
Every 30 minQuota Reserve ▶ Run

OTA Update System

The OTA (over-the-air) system delivers workflow and script updates from fork-sync-all to forks that have opted in. It is the mechanism by which downstream forks stay current without manual merges.


Concepts

UpstreamInterested-Deving-1896/fork-sync-all. The source of truth for all OTA payloads.

Fork — any GitHub repo that has forked fork-sync-all and opted in to OTA.

Payload — a diff of files that changed between the fork's pinned_sha and the latest upstream release tag. Only files the fork hasn't locally modified are included.

Registryconfig/ota-registry.yml. The list of all opted-in repos.

Blocklistconfig/ota-blocklist.yml. Orgs and namespaces excluded from OTA by default (the three mirror-chain orgs and the GitLab namespace).


Lifecycle

Fork owner runs ota-opt-in
        │
        ▼
.ota/config.yml created in fork
Registration PR opened against fork-sync-all
        │
        ▼
PR merged → repo added to config/ota-registry.yml
        │
        ▼
ota-discover.yml (daily) also finds new opt-ins automatically
        │
        ▼
Semver tag pushed to fork-sync-all (v*.*.*)
        │
        ▼
ota-release.yml assembles payload per opted-in repo
Opens PR in each fork with the diff
        │
        ▼
Fork owner merges PR
ota-self-update.yml (runs in fork on schedule) updates pinned_sha

Workflows

ota-opt-in.yml — fork owner runs this once

Propagated to forks via the standalone template profile. The fork owner triggers it via workflow_dispatch. It:

  1. Creates .ota/config.yml in the fork with sensible defaults
  2. Opens a registration PR against fork-sync-all/config/ota-registry.yml

Inputs:

InputDescriptionDefault
upstream_overrideOverride the upstream source (for fork-of-fork cases)(auto-detected)
mirror_chain_opt_inSet true if the fork is in the mirror chainfalse

ota-discover.yml — runs daily in fork-sync-all

Scans GitHub for forks of fork-sync-all that contain .ota/config.yml with enabled: true. For any not already in config/ota-registry.yml, opens a PR to add them.

This is the passive discovery path — fork owners don't need to run ota-opt-in if they create .ota/config.yml manually.

Inputs:

InputDescriptionDefault
dry_runReport new opt-ins without updating registry or opening PRfalse

ota-release.yml — triggered on semver tag push

Triggered when a tag matching v*.*.* is pushed to fork-sync-all. It:

  1. Iterates all repos in config/ota-registry.yml (skipping disabled: true)
  2. For each repo, calls ota-payload-build.sh to assemble the diff
  3. Opens a PR in the fork with the payload
  4. Updates CHANGELOG.md in fork-sync-all

Repos in the blocklist orgs are skipped unless mirror_chain_opt_in: true is set in their .ota/config.yml.


ota-self-update.yml — runs in the fork on a schedule

Propagated to forks via the standalone template profile. Runs on a schedule in the fork. It:

  1. Checks the latest OTA release tag from fork-sync-all
  2. Compares against the fork's pinned_sha in .ota/config.yml
  3. If behind, applies the payload and updates pinned_sha and pinned_at

This is the self-healing path — if a fork owner doesn't merge the OTA PR, ota-self-update will eventually apply the update automatically.


Payload assembly

scripts/ota-payload-build.sh assembles the payload for a single fork:

  1. Detects the fork's upstream parent via GitHub API (or uses upstream_override)
  2. Diffs the fork's current state at pinned_sha against the latest upstream tag
  3. Filters out:
    • Files listed in the fork's exclude_paths
    • Files the fork has locally modified (detected by comparing against upstream)
    • Files owned by template profiles (from config/template-manifest.yml) unless explicitly claimed via workflow_overrides.claim
  4. Applies workflow_overrides.disclaim to remove any files the fork wants to manage independently

The result is a minimal set of files that are safe to overwrite in the fork.


.ota/config.yml reference

Created in the fork by ota-opt-in.yml. All fields except enabled and repo are optional.

enabled: true                  # master switch
repo: "owner/repo-name"        # must match actual GitHub repo
host: "github"                 # "github" only currently
upstream_override: ""          # override upstream detection (fork-of-fork)
pinned_sha: ""                 # managed by ota-self-update — do not edit
pinned_at: ""                  # managed by ota-self-update — do not edit
ota_version: ""                # managed by ota-self-update — do not edit
mirror_chain_opt_in: false     # set true only for mirror-chain repos
workflow_overrides:
  claim: []                    # workflows OTA should manage even if in manifest
  disclaim: []                 # workflows OTA should NOT touch
exclude_paths: []              # glob patterns OTA never writes
include_paths: []              # re-include after exclude_paths

Full field documentation: .ota/schema.yml in this repo.


Blocklist

config/ota-blocklist.yml defines two guards applied before any delivery:

Guard 1 — org/namespace blocklist: The three mirror-chain GitHub orgs (Interested-Deving-1896, OpenOS-Project-OSP, OpenOS-Project-Ecosystem-OOC) and the GitLab namespace (openos-project) are excluded by default. A repo in these orgs can still receive OTA by setting mirror_chain_opt_in: true.

Guard 2 — profile filter: Only repos using the standalone template profile are eligible for OTA. Repos on core, extended, or other profiles are managed by sync-template.yml instead.


Adding a fork to the registry manually

If ota-opt-in is unavailable or the fork owner prefers manual registration:

  1. Create .ota/config.yml in the fork (copy from .ota/schema.yml, set enabled: true and repo)
  2. Add an entry to config/ota-registry.yml:
opted_in:
  - repo: owner/fork-name
    host: github
    registered_at: "2026-06-07"
    pinned_sha: ""
    discovery: false
    mirror_chain_opt_in: false
    disabled: false
  1. Open a PR against fork-sync-allvalidate-config.yml will check the entry.

Disabling OTA for a repo

Set disabled: true in the registry entry. The repo stays registered but receives no further deliveries until re-enabled. Alternatively, set enabled: false in the fork's .ota/config.ymlota-discover will stop treating it as opted-in.

To remove permanently: delete the entry from config/ota-registry.yml.

Registered Imports

All 70 upstream repositories tracked in registered-imports.json. These are synced to Interested-Deving-1896 by sync-registered-imports.yml every 6 hours.

Auto-generated on 2026-06-09 from registered-imports.json.

GitHub (70)

Target repoSource URLAdded
CI-Debuggerhttps://github.com/Interested-Deving-1896/CI-Debugger2026-06-05
CachyOS-PKGBUILDShttps://github.com/CachyOS/CachyOS-PKGBUILDS2026-06-06
CachyOS-Settingshttps://github.com/CachyOS/CachyOS-Settings2026-06-06
New-Cli-Installerhttps://github.com/CachyOS/New-Cli-Installer2026-06-06
OmoiOShttps://github.com/kivo360/OmoiOS2026-06-05
PhoneixAIhttps://github.com/rajshah9305/PhoneixAI2026-06-05
accessibility-agentshttps://github.com/Community-Access/accessibility-agents2026-06-05
actions-orchestratorhttps://github.com/fjaeckel/actions-orchestrator2026-05-25
activitysmith-github-actionhttps://github.com/ActivitySmithHQ/activitysmith-github-action2026-06-05
agentshieldhttps://github.com/affaan-m/agentshield2026-06-05
ananicy-ruleshttps://github.com/CachyOS/ananicy-rules2026-06-06
blueprinthttps://github.com/Narven/blueprint2026-06-05
build-serverhttps://github.com/jakwuh/build-server2026-05-25
cachy-chroothttps://github.com/CachyOS/cachy-chroot2026-06-06
cachy-updatehttps://github.com/CachyOS/cachy-update2026-06-06
cachyos-repo-add-scripthttps://github.com/CachyOS/cachyos-repo-add-script2026-06-06
chwdhttps://github.com/CachyOS/chwd2026-06-06
clahubhttps://github.com/DamageLabs/clahub2026-06-05
clawmetryhttps://github.com/vivekchand/clawmetry2026-06-05
confmgrhttps://github.com/chevdor/confmgr2026-06-05
copr-linux-cachyoshttps://github.com/CachyOS/copr-linux-cachyos2026-06-06
coreutilshttps://github.com/uutils/coreutils2026-06-06
deardirhttps://github.com/deardir/deardir2026-06-05
denoflowhttps://github.com/Interested-Deving-1896/denoflow2026-06-05
directory-modelhttps://github.com/Interested-Deving-1896/directory-model2026-06-05
dirstructxhttps://github.com/Interested-Deving-1896/dirstructx2026-06-05
dotdrophttps://github.com/deadc0de6/dotdrop2026-06-05
fluent-github-actionshttps://github.com/tsirysndr/fluent-github-actions2026-06-05
gavihttps://github.com/homoluctus/gavi2026-06-05
git-queuehttps://github.com/nautilus-cyberneering/git-queue2026-06-08
git-sizerhttps://github.com/github/git-sizer2026-06-07
github-actions-virtualization-supporthttps://github.com/josecelano/github-actions-virtualization-support2026-05-25
github-actions-workflow-tshttps://github.com/emmanuelnk/github-actions-workflow-ts2026-06-05
github-exporterhttps://github.com/githubexporter/github-exporter2026-06-07
graphizehttps://github.com/apvarun/graphize2026-06-05
headroomhttps://github.com/chopratejas/headroom2026-06-02
infra-dashboardhttps://github.com/Interested-Deving-1896/infra-dashboard2026-06-04
katachihttps://github.com/nmicovic/katachi2026-06-05
kde-builderhttps://github.com/KDE/kde-builder2026-06-03
kernel-managerhttps://github.com/CachyOS/kernel-manager2026-06-06
kernel-patcheshttps://github.com/CachyOS/kernel-patches2026-06-06
linux-cachyoshttps://github.com/CachyOS/linux-cachyos2026-06-06
llm-docs-generatorhttps://github.com/Interested-Deving-1896/llm-docs-generator2026-06-05
lowlighter-metricshttps://github.com/lowlighter/metrics2026-06-07
matrix-lockhttps://github.com/rakles/matrix-lock2026-05-25
mdBookhttps://github.com/rust-lang/mdBook2026-06-07
mnemonichttps://github.com/Interested-Deving-1896/mnemonic2026-06-05
models-generatorhttps://github.com/Interested-Deving-1896/models-generator2026-06-05
netclawhttps://github.com/automateyournetwork/netclaw2026-06-05
nexa-gaugehttps://github.com/harnexa/nexa-gauge2026-06-05
niko-claude-skillshttps://github.com/nikotsy/niko-claude-skills2026-05-25
notificarehttps://github.com/joaoGabriel55/notificare2026-06-05
packer-plugin-incuschroothttps://github.com/dontlaugh/packer-plugin-incuschroot2026-06-05
pkgbuild-actionhttps://github.com/CachyOS/pkgbuild-action2026-06-06
project-memory-mcphttps://github.com/Interested-Deving-1896/project-memory-mcp2026-06-05
pylibsmetahttps://github.com/tushkum34-cloud/pylibsmeta2026-06-05
pyverthttps://github.com/Interested-Deving-1896/pyvert2026-06-05
repo-manage-utilhttps://github.com/CachyOS/repo-manage-util2026-06-06
scriptfshttps://github.com/Interested-Deving-1896/scriptfs2026-06-05
scx-managerhttps://github.com/CachyOS/scx-manager2026-06-06
super-yamlhttps://github.com/doriaviram/super-yaml2026-06-05
system-cleanup-managerhttps://github.com/Interested-Deving-1896/system-cleanup-manager2026-06-05
taubytehttps://github.com/Interested-Deving-1896/taubyte2026-06-05
velitehttps://github.com/zce/velite2026-06-05
victoriametrics-metricshttps://github.com/VictoriaMetrics/metrics2026-06-07
xml-yaml-json-converterhttps://github.com/Interested-Deving-1896/xml-yaml-json-converter2026-06-05
y2jhttps://github.com/UltiRequiem/y2j2026-06-05
yaml-fusehttps://github.com/Interested-Deving-1896/yaml-fuse2026-06-05
yaml-sorthttps://github.com/Interested-Deving-1896/yaml-sort2026-06-05
yamllint-tshttps://github.com/Keylan/yamllint-ts2026-06-05

GitLab Subgroup Map

All 158 OSP-bound repositories mapped to their GitLab subgroup under openos-project. This is the single source of truth used by mirror-osp-to-gitlab.sh.

Auto-generated on 2026-06-09 from config/gitlab-subgroups.yml.


git-management_deving

RepoGitHubGitLab
gitlab-enhancedGitHubGitLab

penguins-eggs_deving

RepoGitHubGitLab
eggs-aiGitHubGitLab
eggs-guiGitHubGitLab
oa-toolsGitHubGitLab
penguins-distrobuilderGitHubGitLab
penguins-eggsGitHubGitLab
penguins-eggs-auditGitHubGitLab
penguins-eggs-bookGitHubGitLab
penguins-eggs-integrationsGitHubGitLab
penguins-eggs-prefixGitHubGitLab
penguins-eggs-stage3GitHubGitLab
penguins-immutable-frameworkGitHubGitLab
penguins-incus-platformGitHubGitLab
penguins-kernel-managerGitHubGitLab
penguins-over-the-airGitHubGitLab
penguins-pivotGitHubGitLab
penguins-powerwashGitHubGitLab
penguins-recoveryGitHubGitLab

immutable-filesystem_deving

RepoGitHubGitLab
immutable-linux-frameworkGitHubGitLab

linux-kernel_filesystem_deving

RepoGitHubGitLab
btrfs-dwarfs-frameworkGitHubGitLab
kernel-patchesGitHubGitLab
linux-cachyosGitHubGitLab
linux-distro-prefixGitHubGitLab
linux-distro-stage3GitHubGitLab
linux-over-the-airGitHubGitLab
linux-pivotGitHubGitLab
linux-powerwashGitHubGitLab
liquorix-unified-kernelGitHubGitLab
liqxanmodGitHubGitLab
lkfGitHubGitLab
lkmGitHubGitLab
ukmGitHubGitLab
xanmod-unified-kernelGitHubGitLab

incus_deving

RepoGitHubGitLab
Image-ServerGitHubGitLab
Incus-MacOS-ToolkitGitHubGitLab
K8s-in-incusGitHubGitLab
LXD-ManagerGitHubGitLab
blincusGitHubGitLab
cluster-api-provider-incusGitHubGitLab
ctdeGitHubGitLab
distrobuilderGitHubGitLab
dotdropGitHubGitLab
ezpodman-sandboxGitHubGitLab
gh-actions-managerGitHubGitLab
incusGitHubGitLab
incus-agent-sysvinitGitHubGitLab
incus-app-containerGitHubGitLab
incus-cloud-initGitHubGitLab
incus-composeGitHubGitLab
incus-demo-serverGitHubGitLab
incus-deployGitHubGitLab
incus-dockerGitHubGitLab
incus-ghaGitHubGitLab
incus-goadGitHubGitLab
incus-image-serverGitHubGitLab
incus-imagesGitHubGitLab
incus-init-scriptsGitHubGitLab
incus-launch-vm-actionGitHubGitLab
incus-linux-toolkitGitHubGitLab
incus-osGitHubGitLab
incus-package-repoGitHubGitLab
incus-sdkGitHubGitLab
incus-ui-canonicalGitHubGitLab
incus-waydroid-toolkitGitHubGitLab
incus-windowsGitHubGitLab
incus-windows-toolkitGitHubGitLab
incus_container_gui_setupGitHubGitLab
incus_container_managerGitHubGitLab
incuslabGitHubGitLab
infra-dashboardGitHubGitLab
kapsuleGitHubGitLab
kapsule-incus-managerGitHubGitLab
lxd_incus_gui_appsGitHubGitLab
packer-plugin-incusGitHubGitLab
packer-plugin-incuschrootGitHubGitLab
podclawGitHubGitLab
polarGitHubGitLab
setup-incusGitHubGitLab
talosGitHubGitLab
talos-incusGitHubGitLab
terraform-incus-oci-image-updatingGitHubGitLab
waydroid-toolkitGitHubGitLab

taubyte_deving

RepoGitHubGitLab
taubyteGitHubGitLab

neon-deving

RepoGitHubGitLab
KPortGitHubGitLab
docker-imagesGitHubGitLab
kde-builderGitHubGitLab
pkg-kde-dev-scriptsGitHubGitLab
pkg-kde-jenkinsGitHubGitLab
pkg-kde-toolsGitHubGitLab
qt-kde-team.pages.debian.netGitHubGitLab
ubuntu-coreGitHubGitLab

ops

RepoGitHubGitLab
build-serverGitHubGitLab
flatpak-repoGitHubGitLab
fork-sync-allGitHubGitLab
git-sizerGitHubGitLab
github-exporterGitHubGitLab
headroomGitHubGitLab
lowlighter-metricsGitHubGitLab
org-mirrorGitHubGitLab
victoriametrics-metricsGitHubGitLab

yaml-tooling_deving

RepoGitHubGitLab
CI-DebuggerGitHubGitLab
actions-orchestratorGitHubGitLab
activitysmith-github-actionGitHubGitLab
agentshieldGitHubGitLab
blueprintGitHubGitLab
confmgrGitHubGitLab
deardirGitHubGitLab
denoflowGitHubGitLab
directory-modelGitHubGitLab
dirstructxGitHubGitLab
fluent-github-actionsGitHubGitLab
gaviGitHubGitLab
git-queueGitHubGitLab
github-actions-virtualization-supportGitHubGitLab
github-actions-workflow-tsGitHubGitLab
graphizeGitHubGitLab
katachiGitHubGitLab
llm-docs-generatorGitHubGitLab
matrix-lockGitHubGitLab
mnemonicGitHubGitLab
models-generatorGitHubGitLab
pkgbuild-actionGitHubGitLab
project-memory-mcpGitHubGitLab
pyvertGitHubGitLab
scriptfsGitHubGitLab
super-yamlGitHubGitLab
system-cleanup-managerGitHubGitLab
veliteGitHubGitLab
xml-yaml-json-converterGitHubGitLab
y2jGitHubGitLab
yaml-fuseGitHubGitLab
yaml-sortGitHubGitLab
yamlerGitHubGitLab
yamllint-tsGitHubGitLab

cachyos_deving

RepoGitHubGitLab
CachyOS-PKGBUILDSGitHubGitLab
CachyOS-SettingsGitHubGitLab
New-Cli-InstallerGitHubGitLab
ananicy-rulesGitHubGitLab
cachy-chrootGitHubGitLab
cachy-updateGitHubGitLab
cachyos-repo-add-scriptGitHubGitLab
chwdGitHubGitLab
copr-linux-cachyosGitHubGitLab
kernel-managerGitHubGitLab
repo-manage-utilGitHubGitLab
scx-managerGitHubGitLab

ai-agents_deving

RepoGitHubGitLab
OmoiOSGitHubGitLab
PhoneixAIGitHubGitLab
accessibility-agentsGitHubGitLab
clahubGitHubGitLab
clawmetryGitHubGitLab
netclawGitHubGitLab
nexa-gaugeGitHubGitLab
niko-claude-skillsGitHubGitLab
notificareGitHubGitLab
pylibsmetaGitHubGitLab

rust-systems_deving

RepoGitHubGitLab
coreutilsGitHubGitLab
mdBookGitHubGitLab

Pre-Flush Checklist

Steps to verify before triggering pre-flush-prep.yml or full-chain-flush.yml. Most of these are zero-API-call operations — safe to run while quota is exhausted.


1. Check quota

curl -sf -H "Authorization: token $SYNC_TOKEN" \
  "https://api.github.com/rate_limit" | \
  python3 -c "
import sys, json
from datetime import datetime, timezone
d = json.load(sys.stdin)
core = d['resources']['core']
reset = datetime.fromtimestamp(core['reset'], tz=timezone.utc)
now = datetime.now(tz=timezone.utc)
eta = max(0, int((reset - now).total_seconds()))
print(f'Remaining : {core[\"remaining\"]}/{core[\"limit\"]}')
print(f'Reset at  : {reset.strftime(\"%H:%M:%S UTC\")}')
print(f'ETA       : {eta//60}m {eta%60}s')
"

pre-flush-prep requires at least 500 remaining to proceed past its quota pre-flight. Below that it skips entirely. The flush itself needs ~1000–1500 to complete a full run without hitting the reserve floor mid-way.


2. Run config validators

All must pass with zero errors. Warnings on validate-workflow-guards are acceptable if they are pre-existing (check git log to confirm).

python3 scripts/validate-gitlab-subgroups.py config/gitlab-subgroups.yml
python3 scripts/validate-registered-imports.py registered-imports.json
python3 scripts/validate-cost-profiles.py config/workflow-cost-profiles.yml
python3 scripts/validate-priority-tiers.py config/workflow-priority-tiers.yml
python3 scripts/validate-template-config.py
python3 scripts/validate-workflow-guards.py

Expected output pattern:

config/gitlab-subgroups.yml: 12 subgroups, N repos — ✅ Valid
validate-registered-imports: N entry/entries valid
validate-cost-profiles: N profile(s) valid
validate-priority-tiers: N entries valid (tier1=N, tier2=N, tier3=N, tier4=N)
validate-template-config: N profile(s) valid, N consumer(s) valid
validate-workflow-guards: all checks passed (N workflows, ...)

3. Run the test suites

Python validators:

python3 -m pytest tests/ -v --tb=short

Bash check-readme-render.sh self-test (checks 13–22, mobile/cross-engine):

bash scripts/tests/test-check-readme-render-mobile.sh

All 213 pytest tests and all 24 shell tests must pass. A failure here means a script or config change broke something the validators don't catch.


4. ShellCheck modified scripts

Run against any scripts changed since the last flush:

git diff --name-only HEAD~5 -- 'scripts/*.sh' | xargs shellcheck --severity=warning

Or check the three most commonly modified ones directly:

shellcheck --severity=warning \
  scripts/import-repo.sh \
  scripts/merge-to-monorepo.sh \
  scripts/check-vendor-agnostic.sh

SC1091 (info severity, not following sourced files) is expected and acceptable across all scripts that source includes/budget.sh or includes/gh-api.sh.


5. Check for open PRs

Open PRs on main that are green and mergeable should be merged before the flush so the flush runs against the latest state. pre-flush-prep Step 2 auto-merges eligible PRs, but it's cleaner to do it manually if you're already reviewing.

Dependency-update PRs (e.g. chore(deps): update workflow dependencies) are safe to merge without review — they only bump actions/* versions.


6. Check vendor/ agnostic state

bash scripts/check-vendor-agnostic.sh vendor

Must exit 0. Any violations mean a vendored component has deployment-identity values hardcoded as fallback defaults — fix before flushing so the component is deployable by anyone.


7. Verify working tree is clean

git status --short
git log --oneline -5

Uncommitted changes won't be picked up by the flush. Commit or stash everything before triggering.


8. Trigger

Once all the above are green:

  1. Go to pre-flush-prep.yml
  2. Click Run workflow
  3. Leave all inputs at defaults for a standard flush
  4. Optional inputs:
    • skip_merge_prs=true — skip Step 2 if you've already merged PRs manually
    • skip_cleanup=true — skip branch/run cleanup if quota is tight
    • quota_wait_min — lower from 60 if quota is already healthy

pre-flush-prep dispatches full-chain-flush automatically at the end of Step 4. You do not need to trigger full-chain-flush directly.


Quick reference — what each step of pre-flush-prep does

StepActionSkippable
1Cancel stale/queued runs older than STALE_MINNo
2Merge green PRs on mainskip_merge_prs=true
3Validate all configs (same as step 2 above)No
4Clean up stale branches and redundant base reposskip_cleanup=true
5Dispatch full-chain-flushNo

Runbooks

Operational procedures for common and emergency situations.


Quota exhaustion

Symptoms: workflows fail with 403, gh api calls return empty, validate-config skips with "quota too low".

Check current state:

curl -sf -H "Authorization: token $SYNC_TOKEN" \
  "https://api.github.com/rate_limit" | \
  python3 -c "
import sys, json
from datetime import datetime, timezone
d = json.load(sys.stdin)
core = d['resources']['core']
reset = datetime.fromtimestamp(core['reset'], tz=timezone.utc)
now = datetime.now(tz=timezone.utc)
eta = max(0, int((reset - now).total_seconds()))
print(f'Remaining : {core[\"remaining\"]}/{core[\"limit\"]}')
print(f'Reset at  : {reset.strftime(\"%H:%M:%S UTC\")}')
print(f'ETA       : {eta//60}m {eta%60}s')
"

Recovery:

  1. Wait for the reset (up to 1 hour). The reset time is shown above.
  2. While waiting, use the time productively — all local operations (config validation, ShellCheck, pytest, file edits) work without quota.
  3. After reset, trigger pre-flush-prep.yml to clear the queue and restart the mirror chain cleanly.

Prevention: quota-reserve.yml cancels low-priority runs at < 1000 remaining. If exhaustion is recurring, check config/workflow-cost-profiles.yml for unexpectedly expensive workflows and consider raising MIN_QUOTA thresholds.


Queue pile-up

Symptoms: many workflows stuck in "queued" state, runners appear busy but nothing is completing.

Check:

# Via GitHub CLI (requires quota)
gh run list --repo Interested-Deving-1896/fork-sync-all --status queued --limit 50

Recovery:

  1. Trigger queue-manager.yml manually — it deduplicates and evicts runs queued > 25 minutes.
  2. If the queue is severely backed up, trigger pre-flush-prep.yml with skip_merge_prs=true and skip_cleanup=true — Step 1 aggressively clears stale runs before dispatching the flush.
  3. As a last resort, trigger critical-deploy.yml — it performs an aggressive queue clear and dispatches with priority.

Token expiry

Symptoms: token-health.yml opens an issue labelled token-monitor, or workflows fail with 401.

Check expiry:

bash scripts/token-monitor.sh

Rotate a token:

  1. Generate a new PAT at https://github.com/settings/tokens
  2. Go to rotate-token.ymlRun workflow
  3. Select the secret name from the dropdown
  4. Paste the new token value
  5. Leave validate checked
  6. Update the expiry date in AGENTS.md token rotation table

For OSP org secrets (MIRROR_TOKEN, ORG_MIRROR_OSP_TO_OOC), see the Token Rotation section in AGENTS.md — these require a separate PAT with admin:org on OpenOS-Project-OSP.


Mirror chain broken

Symptoms: repos in OSP or OOC are behind I-D-1896 by more than one cycle, or GitLab mirrors show stale commits.

Diagnose:

# Check GitLab sync status (requires quota)
gh workflow run check-gitlab-sync.yml --repo Interested-Deving-1896/fork-sync-all

Recovery by leg:

Broken legFix
I-D-1896 → OSPTrigger mirror-to-osp.yml manually
OSP → OOCTrigger mirror-osp-to-ooc.yaml manually
OSP → GitLabTrigger mirror-osp-to-gitlab.yml manually
GitLab → I-D-1896Trigger sync-from-gitlab.yml manually

For a full chain reset, trigger full-chain-flush.yml directly (or via pre-flush-prep.yml for a clean pre-flight first).


Config validation failure

Symptoms: validate-config.yml fails on push, blocking the flush.

Run locally to see the error:

python3 scripts/validate-gitlab-subgroups.py config/gitlab-subgroups.yml
python3 scripts/validate-registered-imports.py registered-imports.json
python3 scripts/validate-cost-profiles.py config/workflow-cost-profiles.yml
python3 scripts/validate-priority-tiers.py config/workflow-priority-tiers.yml
python3 scripts/validate-template-config.py
python3 scripts/validate-workflow-guards.py

Common causes:

  • Duplicate repo name in gitlab-subgroups.yml
  • Duplicate source_url or target_name in registered-imports.json
  • Workflow added to .github/workflows/ but not registered in workflow-priority-tiers.yml or workflow-sync.yml
  • Duplicate name in workflow-priority-tiers.yml

Vendor component agnostic check failure

Symptoms: enforce-agnostic-vendor.yml fails on a PR touching vendor/.

Run locally:

bash scripts/check-vendor-agnostic.sh vendor

The output shows the exact file, line, and category of violation. Fix by:

  • Removing the hardcoded fallback value (set to empty string)
  • Moving the value to a CI variable / repo var
  • Adding # check-vendor-agnostic: ignore if the value is genuinely deployment-agnostic (rare — document why)

README render failure

Symptoms: validate-readme-render.yml fails on a PR.

Run locally:

bash scripts/check-readme-render.sh README.md
# Also run the self-test to verify the checker itself is working:
bash scripts/tests/test-check-readme-render-mobile.sh

Common causes: unclosed fences, leaked log lines, bare [text] links without URLs, raw angle brackets, broken tables, missing H1.


OTA delivery failure

Symptoms: ota-release.yml fails for one or more forks, or a fork's ota-self-update.yml fails.

For a single fork:

  1. Check the fork's ota-self-update.yml run logs for the specific error
  2. Common causes: fork has diverged significantly, pinned_sha is stale, or the fork's .ota/config.yml has an invalid field
  3. To reset: update pinned_sha in the fork's .ota/config.yml to the current upstream HEAD SHA, then re-trigger ota-self-update.yml

To skip a fork temporarily: Set disabled: true in its config/ota-registry.yml entry.

To re-deliver to all forks: Push a new semver tag to fork-sync-allota-release.yml triggers automatically.


Incident response checklist

For any production incident affecting the mirror chain:

  1. Check quota — if exhausted, wait for reset before doing anything else
  2. Check queue — trigger queue-manager.yml to clear pile-ups
  3. Identify the broken leg — use check-gitlab-sync.yml and manual inspection
  4. Fix the specific leg — trigger the relevant mirror workflow directly
  5. Validate config — run all validators locally before triggering a flush
  6. Run pre-flush-prep — let it clean up and restart the chain
  7. Monitor — watch the first few workflow runs after recovery for secondary failures

Quota Cost Registry

GitHub's REST API allows 5,000 requests per hour per user (shared across all tokens belonging to the same user). This page documents how many REST calls each workflow consumes per run, the minimum quota required before a workflow should start, and how the quota management system uses this data.

GraphQL counts as 1 call regardless of how many repos are queried. raw.githubusercontent.com fetches are exempt entirely.


How quota is managed

Three mechanisms work together:

MechanismWhereWhat it does
quota-reserve.shRuns every 30 minCancels queued low-priority runs when remaining < RESERVE_FLOOR (default: 1000). Uses min_quota per workflow to also cancel runs that couldn't succeed with current quota even if they started.
budget_check()Inside each script loopStops processing mid-run when time budget is exhausted. Prevents a single run from consuming all quota in one shot.
workflow_min_quota()Pre-flight stepsReturns the min_quota for a workflow from config/workflow-quota-costs.yml. Workflows can use this to skip themselves when quota is too low.

The single source of truth for costs is config/workflow-quota-costs.yml.


Cost table

Costs are estimated from code audit (Phase 1). Phase 2 will replace these with observed p50/p95 values from actual run measurements.

min_quota = minimum REST calls required before this workflow should be allowed to start.

Tier 1 — Critical (never cancelled)

Workflowmin_quotaLowMidHighNotes
Rotate Secret Token5051020Token validation + secret update
Queue Manager5051530Queued run list + cancel calls
Quota Reserve101515rate_limit check (exempt) + cancels
Rate-Limit Re-trigger5052050Failed run scan + dispatch calls
Token Health Monitor5051020Token validation only
CI502510ShellCheck + lint, minimal API
Pre-Flush Prep100103060PR list + check-run queries

Tier 2 — High

Workflowmin_quotaLowMidHighNotes
Mirror Interested-Deving-1896 → OSP50020802002 GraphQL + 1 REST/repo (check-runs, gated)
Mirror OSP → GitLab300520501 GraphQL for repo list; GitLab calls exempt
Sync Registered Imports200515301 GraphQL prefetch; REST only for new repos
Sync All Forks500502005001 GraphQL + 1 REST merge-upstream per fork
Full Chain Flush10001004001000Orchestrates chain — cost is additive
Add Mirror Repo200103060Repo creation + webhook + dispatch

Tier 3 — Medium

Workflowmin_quotaLowMidHighNotes
Update READMEs30050150300Tree fetch + file reads/writes per repo
Create Missing READMEs2002080200Same as Update READMEs, subset of repos
Inject Built-with-Ona Badges200530801 GraphQL (repo list + README); REST only on write
Reconcile Org References30010601501 GraphQL repo list; pushedAt from cache
Check OSP-Bound CI Status300501503004 REST/repo (check-runs not in GraphQL)
Rebase PRs10052050PR list + rebase trigger
Sync btrfs-devel Branches10052050Branch sync per tracked branch
Sync pieroproietti Forks1001040100merge-upstream per fork branch
Setup OSP Mirror Workflows20020802001 GraphQL + workflow/secrets per repo (not in GraphQL)
Upstream PRs from OSP + OOC2002080200PR creation/update per diverged repo
Upstream Direct Commits from OSP + OOC2002080200Commit compare + PR creation
Sync to GitLab10052050GitHub reads; GitLab writes exempt
Sync to GitLab Variant10052050Same as Sync to GitLab
Sync from GitLab10052050GitLab reads + GitHub writes
Notification Poller501515Single notifications call + optional dispatch

Tier 4 — Low (cancelled first)

Workflowmin_quotaLowMidHighNotes
Translate READMEs1001040100File read + write per README
LTS README Standardisation1001040100File read + write per LTS repo
Generate OSP Dependency Graph1002060150README + package.json reads per repo
Upstream Workflow Proposal5052050Workflow file reads + PR creation
Update Infrastructure Dependencies5051530Dependabot config + PR creation
Mirror Artifacts20010501502 GraphQL; release asset downloads exempt
Mirror Releases20010501502 GraphQL + 1 REST releases list per repo
Cleanup Stale Branches20010602001 GraphQL + 1 REST compare per branch
OTA Discover1001040100Fork list + config reads per fork
OTA Self-Update5051530Config read + PR creation
Mirror Orgs1002060150Repo list + description reads per org
Resolve CI Failures1001040100Failed run list + job details + file writes

Daily quota budget

At 5,000 calls/hour reset, the effective daily budget depends on how many resets are consumed cleanly vs. drained by backlog. With the schedule reductions applied (June 2026), the expected daily workflow run count dropped by ~172 runs/day.

CategoryBeforeAfter
quota-reserve runs/day14448
queue-manager runs/day9648
notify-poller runs/day126
rate-limit-rerun runs/day126
mirror-artifacts runs/day63
mirror-osp-to-gitlab runs/day63
sync-pieroproietti-forks runs/day63
sync-to-gitlab-variant runs/day63

REST → GraphQL conversion log

Scripts converted from per-repo REST loops to batched GraphQL calls:

ScriptSavings/runRuns/daySaved/day
sync-registered-imports.sh~1004~400
mirror-osp-to-gitlab.sh~23~6
reconcile-org-refs.sh~1006~600
inject-badges.sh~503~150
cleanup-branches.sh~2001~200
mirror-releases.sh~504~200
mirror-artifacts.sh~503~150
ota-discover.sh~501~50
mirror-orgs.sh~601~60
setup-osp-mirrors.sh~1004~400
Total~2,216/day

Phase 2: observed cost tracking (planned)

Phase 2 will add lightweight instrumentation to measure actual REST consumption per run:

  1. scripts/includes/quota-instrument.sh — records remaining_before and remaining_after as workflow step summary annotations
  2. update-quota-costs.yml — weekly workflow that reads the last 30 run summaries via GraphQL, computes p50/p95 per workflow, and commits updated values back to config/workflow-quota-costs.yml with basis: observed

Once Phase 2 is active, the tables above will show observed values alongside the code-audit estimates, and quota-reserve.sh will automatically use the more accurate figures.

Operational Reference: GitHub Actions Limits & Quotas

This document covers the GitHub Actions limits that affect fork-sync-all, what consumes them, how to detect exhaustion, and how to recover.


GitHub API Rate Limit

Quota: 5,000 requests/hour per authenticated user token.

Resets: Top of every hour (rolling window).

What consumes it:

OperationCost
gh api / REST API call1 req
Listing workflow runs1 req per page
Cancelling a run1 req
Triggering a workflow dispatch1 req
Checking job status1 req per job
GraphQL querySeparate quota (5,000 points/hr) — unaffected by REST exhaustion

How fork-sync-all burns it:

  • Every workflow_run trigger fires a new run, which itself may call the API
  • rate-limit-rerun.yml (formerly hourly) scans all recent failed runs
  • stuck-run-detector.yml (formerly hourly) lists all queued/in-progress runs
  • translate-readmes.yml was triggering after 10 workflows — each trigger consumed dozens of API calls for the org scan
  • Bulk-cancelling queued runs during cleanup consumes ~1 req per cancel — if the queue is large and quota is already low, the cancel loop itself can exhaust the remaining quota

Detecting exhaustion:

gh api rate_limit --jq '.resources.core | "remaining: \(.remaining)/\(.limit)  resets: \(.reset | todate)"'

Recovery: Wait until the top of the next hour. GraphQL remains available during REST exhaustion and can be used for read-only queries.


GitHub Actions Runner Minutes

Free tier: 2,000 minutes/month. Resets on your billing cycle date (the day of the month your GitHub account was created — check Settings → Billing → Actions for the exact date).

Paid: Billed per minute beyond the free tier; Linux runners cost 1×, Windows 2×, macOS 10×. All workflows in this repo use ubuntu-latest (Linux, 1×).

What counts against the monthly quota:

  • Every job that runs on ubuntu-latest (GitHub-hosted runner)
  • Time is measured from job start to job end, rounded up to the nearest minute
  • Jobs that are queued but never start do not consume minutes
  • Jobs that exit immediately (e.g. if: condition is false at the job level) still consume ~1 minute for runner provisioning

What does NOT count:

  • workflow_dispatch triggers that are never clicked
  • Runs that are cancelled before a job starts
  • Skipped jobs (if: evaluated to false before the runner is assigned)
  • Self-hosted runners (zero cost regardless of usage)

How fork-sync-all was burning minutes (before May 2026 fixes):

  1. mirror-orgs-watchdog fired after every mirror completion (5 workflows × hourly cadence = ~120 runs/day), each consuming ~1 min even on success
  2. update-readmes triggered after 7 workflows including high-frequency syncs
  3. inject-badges triggered after mirror workflows that run hourly
  4. stuck-run-detector and rate-limit-rerun ran hourly as meta-workflows, each consuming minutes to manage other workflows
  5. workflow_run listeners fired on every completed event (success, failure, cancelled) — not just on the outcomes they actually needed

Detecting exhaustion:

Symptoms (in order of appearance):

  1. ubuntu-latest jobs queue but never start
  2. No in-progress runs despite many queued
  3. Runs queued for hours with 0 runners active
  4. Billing API returns 404 (needs user OAuth scope — check web UI instead)

Check via GitHub web UI: Settings → Billing → Actions.

Recovery: Wait until the billing cycle reset date. In the meantime:

  • Cancel all queued runs (they will never start)
  • Do not push commits that trigger new workflow runs
  • Use workflow_dispatch manually only for critical operations

Concurrency Groups & Stuck Runs

How they work: A concurrency group allows only one run at a time for a given key. If cancel-in-progress: false, a second run queues behind the first. If the first run never finishes (e.g. runner minutes exhausted mid-job), the queued run is permanently stuck.

The cascade pattern:

  1. Runner minutes exhaust mid-job → job hangs in in_progress
  2. Next scheduled run queues behind it (cancel-in-progress: false)
  3. The in-progress run never finishes → queue grows indefinitely
  4. API calls to cancel are themselves rate-limited → nothing can be cleared

Orphaned runs: A run can become permanently orphaned if it was triggered from an older version of a workflow file that contained a job (e.g. Update cost profile) that no longer exists in the current file. The run accepts cancel API calls but GitHub immediately re-queues it because the concurrency group from the old code is still technically active. These runs time out automatically after GitHub's maximum queue wait (~6 hours). New runs from the same workflow are not blocked — they use the current file.

Policy in this repo (May 2026): All workflows use cancel-in-progress: true except those that perform multi-repo writes where mid-run cancellation would leave state partially applied:

Workflowcancel-in-progressReason
sync-templatefalsePropagates files to 35 repos — partial sync leaves repos inconsistent
mirror-releasesfalsePartial mirror leaves releases incomplete
lts-readmesfalseMid-run cancel leaves some repos un-standardised
mirror-osp-to-gitlabfalsePartial GitLab mirror
create-readmesfalseMid-run cancel leaves some repos without READMEs
mirror-artifactsfalsePartial artifact mirror
All otherstrueNewer run supersedes safely

Detecting stuck runs:

gh api "repos/Interested-Deving-1896/fork-sync-all/actions/runs?per_page=100" \
  --jq '[.workflow_runs[] | select(.status == "queued")] | length'

Bulk cancel (check quota first — cancel loop consumes ~1 req per run):

gh api rate_limit --jq '.resources.core.remaining'

gh api "repos/Interested-Deving-1896/fork-sync-all/actions/runs?per_page=100" \
  --jq '[.workflow_runs[] | select(.status=="queued") | .id] | .[]' | \
  xargs -I{} gh api -X POST \
    "repos/Interested-Deving-1896/fork-sync-all/actions/runs/{}/cancel"

workflow_run Trigger Cost Model

workflow_run fires on every completed event regardless of conclusion (success, failure, cancelled, skipped). A listener that only needs to act on failures still consumes a runner minute for every successful upstream run unless gated at the job level.

Pattern used in this repo:

# For workflows that act on upstream SUCCESS (content processors):
jobs:
  my-job:
    if: |
      github.event_name != 'workflow_run' ||
      github.event.workflow_run.conclusion == 'success'

# For workflows that act on upstream FAILURE (watchdogs/retriers):
jobs:
  retry:
    if: |
      github.event_name == 'workflow_dispatch' ||
      github.event.workflow_run.conclusion == 'failure'

This exits immediately (no runner cost) when the conclusion doesn't match, while keeping the trigger automatic.

All workflow_run listeners and their gates (May 2026):

WorkflowGate
mirror-orgs-watchdogconclusion == 'failure'
create-readmesconclusion == 'success'
inject-badgesconclusion == 'success'
lts-readmesconclusion == 'success'
mirror-osp-to-gitlabconclusion == 'success'
translate-readmesconclusion == 'success' (on gate job)
update-readmesconclusion == 'success'
dwarfs-pack-callerconclusion == 'success'
rebase-ltsconclusion == 'success'

Current Workflow Schedule Summary

Workflows that run on a schedule and their cadence after May 2026 fixes:

WorkflowScheduleNotes
mirror-to-ospHourly :00Core mirror
mirror-releasesHourly :00
mirror-artifactsHourly :08
mirror-osp-to-gitlabHourly :24
upstream-prsHourly :32
upstream-commitsHourly :40
reconcile-org-refsHourly :56
sync-pieroproietti-forksHourly :05
notify-pollerEvery 30 min
stuck-run-detectorEvery 6h :20Reduced from hourly
rate-limit-rerunEvery 6h :12Reduced from hourly
rate-limit-budget-reportDaily 11:00Reduced from every 2h
update-readmesDaily 10:20
create-readmesDaily 10:30
inject-badgesDaily 10:40
translate-readmesDaily 10:50
sync-forksDaily 11:30
mirror-orgs-fullDaily 10:00
lts-readmesMonthly 1st

Hourly workflows are the primary minute consumers. At ~1 min/run, 8 hourly workflows = ~192 min/day = ~5,760 min/month — well over the 2,000 min free tier. A paid plan or self-hosted runner is required for this repo's workload.


To eliminate the monthly minute cap entirely, add a self-hosted runner:

  1. Go to Settings → Actions → Runners → New self-hosted runner
  2. Follow the setup instructions for your host OS
  3. Change workflow runs-on from ubuntu-latest to self-hosted (or add a label and use that label)

Self-hosted runners have no minute cost and no concurrent job cap beyond what the host machine can handle.


Quick Reference: Limit Reset Times

LimitResets
GitHub API rate limit (REST)Top of every hour
GitHub API rate limit (GraphQL)Top of every hour (separate quota)
GitHub Actions minutesBilling cycle date (check Settings → Billing)
GitHub Actions concurrent jobs (free)N/A — blocked by minute exhaustion

Contributing

Conventions for adding workflows, scripts, config entries, and vendor components.


Adding a workflow

  1. Create the workflow file in .github/workflows/

  2. Register in priority tiers — add an entry to config/workflow-priority-tiers.yml using the workflow's name: field (not the filename):

    - name: "My New Workflow"
      tier: 3   # MEDIUM — adjust based on criticality
    

    Tier guide: 1=CRITICAL (never cancelled), 2=HIGH (mirror chain), 3=MEDIUM (default), 4=LOW (cancelled first under quota pressure)

  3. Register in workflow-sync — add to config/workflow-sync.yml:

    • Under github_only if it has no GitLab CI counterpart (most workflows)
    • Under paired if it has a matching GitLab CI job
  4. Add a concurrency group if triggered by schedule or workflow_run:

    concurrency:
      group: my-workflow-name
      cancel-in-progress: true
    
  5. Add a quota pre-flight if the workflow makes API calls and runs frequently:

    - name: Check quota
      id: quota
      run: |
        remaining=$(curl -sf -H "Authorization: token $GH_TOKEN" \
          "https://api.github.com/rate_limit" | jq '.resources.core.remaining')
        echo "remaining=$remaining" >> "$GITHUB_OUTPUT"
        [[ "$remaining" -lt 500 ]] && echo "skip=true" >> "$GITHUB_OUTPUT" || echo "skip=false" >> "$GITHUB_OUTPUT"
    
  6. Validate:

    python3 scripts/validate-workflow-guards.py
    python3 scripts/validate-priority-tiers.py config/workflow-priority-tiers.yml
    

Adding a script

Scripts live in scripts/. All logging must go to stderr — never stdout — because many functions are called inside $(...) captures where stdout becomes the captured value.

info() { echo "[my-script] $*" >&2; }
warn() { echo "[warn] $*" >&2; }

If the script sources includes/budget.sh or includes/gh-api.sh, add the shellcheck directive:

# shellcheck source=includes/budget.sh
source "$(dirname "${BASH_SOURCE[0]}")/includes/budget.sh"

Run ShellCheck before committing:

shellcheck --severity=warning scripts/my-script.sh

Adding a config entry

New repo to GitLab mirror chain

Add to config/gitlab-subgroups.yml under the appropriate subgroup:

  rust-systems_deving:
    repos:
      - my-new-repo

Then validate:

python3 scripts/validate-gitlab-subgroups.py config/gitlab-subgroups.yml

New repo to upstream sync

Add to registered-imports.json:

{
    "source_url": "https://github.com/upstream-org/repo-name",
    "target_name": "repo-name",
    "platform": "github",
    "added": "2026-06-07T00:00:00Z"
}

Then validate:

python3 scripts/validate-registered-imports.py registered-imports.json

New workflow priority tier entry

See "Adding a workflow" above — step 2.


Adding a vendor component

vendor/ is for third-party components that fork-sync-all hosts or deploys. It is not for first-party scripts or config.

Before adding:

  • Confirm the component is genuinely third-party (not a script you wrote)
  • Confirm it will be deployed or served by fork-sync-all (not just referenced)

When adding:

  1. Place under vendor/<component-name>/
  2. Strip all distro-specific or org-specific hardcoded defaults — see the agnostic rule below
  3. Add a README.md with a "Before the first deploy" section covering all required CI variables
  4. Run the agnostic check:
    bash scripts/check-vendor-agnostic.sh vendor/<component-name>
    

Agnostic rule

No deployment-identity values may appear as hardcoded fallback defaults in vendored components. This includes:

  • Public URLs: ${VITE_ENDPOINT_URL:-https://api.myorg.com}
  • Org/repo slugs: ${MIRRORLIST_REPO:-MyOrg/my-repo}
  • Arch/repo paths: ${MIRROR_REPO_PATHS:-x86_64/core}
  • Distro names: ${DISTRO:-cachyos}

Allowed:

  • Localhost dev URLs: ${API_URL:-http://localhost:5862}
  • Generic paths: ${MIRRORLIST_PATH:-mirrorlist/mirrorlist}
  • Single-word tokens: ${LOG_LEVEL:-info}
  • UI strings: ${APP_NAME:-Infra Dashboard}

To suppress a specific line that is intentionally non-agnostic:

SOME_VAR="${SOME_VAR:-value}"  # check-vendor-agnostic: ignore

enforce-agnostic-vendor.yml runs automatically on every push/PR touching vendor/.


Commit conventions

Follow the existing commit message style:

scope: short description

Longer explanation if needed. Focus on why, not what.

Common scopes: fix, feat, config, docs, vendor, scripts, ci.


Before opening a PR

# Config validators
python3 scripts/validate-gitlab-subgroups.py config/gitlab-subgroups.yml
python3 scripts/validate-registered-imports.py registered-imports.json
python3 scripts/validate-cost-profiles.py config/workflow-cost-profiles.yml
python3 scripts/validate-priority-tiers.py config/workflow-priority-tiers.yml
python3 scripts/validate-template-config.py
python3 scripts/validate-workflow-guards.py

# Test suites
python3 -m pytest tests/ -v --tb=short
bash scripts/tests/test-check-readme-render-mobile.sh

# ShellCheck (for any .sh files changed)
git diff --name-only HEAD -- 'scripts/*.sh' | xargs shellcheck --severity=warning

# Vendor check (if vendor/ was touched)
bash scripts/check-vendor-agnostic.sh vendor

# README render check
bash scripts/check-readme-render.sh README.md

All must pass before the PR is ready for merge.