fork-sync-all
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
- Full documentation — architecture, quota management, workflow reference, runbooks
- Workflow Triggers — every workflow, its schedule, and what else triggers it (plain text)
Workflows
Sync & Mirror
| Workflow | Schedule | What it does |
|---|---|---|
sync-forks.yml | Daily 06:00 | Syncs all Interested-Deving-1896 forks with their upstreams |
sync-pieroproietti-forks.yml | Every 4h :05 | Fast-path sync for pieroproietti forks only |
mirror-to-osp.yml | Every 6h :00 | Mirrors Interested-Deving-1896 repos into OpenOS-Project-OSP |
mirror-osp-to-gitlab.yml | Every 4h :30 | Mirrors OpenOS-Project-OSP repos into GitLab openos-project |
sync-from-gitlab.yml | Daily 04:22 | Pulls GitLab openos-project repos back into Interested-Deving-1896 (scheduled fallback; primary trigger is GitLab CI on push) |
sync-registered-imports.yml | Every 6h :55 | Re-syncs all repos registered via the import workflow |
Import
| Workflow | Trigger | What it does |
|---|---|---|
import-repo.yml | Manual | Imports 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 inInterested-Deving-1896(defaults to source name)mirror_to_osp_ooc— push through the OSP → OOC chain immediatelyongoing_sync— register inregistered-imports.jsonfor re-sync every 6h
Quota and queue management
| Workflow | Schedule | What it does |
|---|---|---|
full-chain-flush.yml | On validate-config success / manual | Master orchestrator — runs the full mirror chain in sequence |
queue-manager.yml | Every 15 min | Deduplicates queued runs; evicts runs queued > 25 min |
quota-reserve.yml | Every 10 min | Cancels low-priority queued runs when quota < 1000 |
validate-config.yml | On push / PR | Validates all config files; runs AgentShield security scan (opt-in) |
Security and token management
| Workflow | Schedule | What it does |
|---|---|---|
token-health.yml | Weekly Monday 09:00 | Checks GitHub + GitLab PAT expiry; opens an issue at 45 days out |
rotate-token.yml | Manual | Rotates any repo secret via workflow dispatch |
Maintenance
| Workflow | Schedule | What it does |
|---|---|---|
reconcile-org-refs.yml | Manual / on push | Rewrites org names in file content across all three orgs |
upstream-commits.yml | Every 6h :47 | Detects direct commits to OSP/OOC and opens PRs in Interested-Deving-1896 |
upstream-prs.yml | Every 6h :33 | Syncs open PRs from OSP/OOC upstream into Interested-Deving-1896 |
add-mirror-repo.yml | Manual | Adds a new repo to the OSP + OOC mirror chain |
setup-osp-mirrors.yml | Manual | Injects mirror-osp-to-ooc.yaml into all OSP repos |
resolve-failures.yml | Daily 07:30 | AI-assisted CI failure resolver (GitHub Models) |
upstream-workflow-proposal.yml | Weekly Monday 06:00 | Scans OSP-bound repos for new workflows; opens a PR to propose as a template skeleton |
rebase-lts.yml | Weekly | Rebases the lts branch of penguins-eggs |
sync-eggs-docs-to-book.yml | On push | Syncs penguins-eggs docs into penguins-eggs-book |
mirror-artifacts.yml | Scheduled | Mirrors release artifacts (packages, containers, flatpaks) |
ota-discover.yml | Scheduled | Discovers OTA update payloads across OSP-bound repos |
Secrets
| Secret | Used by | Notes |
|---|---|---|
SYNC_TOKEN | All workflows | GitHub PAT — repo + workflow + admin:org scopes |
GH_SYNC_TOKEN | GitLab CI sync-from-gitlab job | Same PAT stored as a GitLab CI variable |
GITLAB_SYNC_TOKEN | mirror-osp-to-gitlab.yml, sync-from-gitlab.yml | GitLab PAT — api + write_repository on openos-project group |
BITBUCKET_TOKEN | import-repo.yml, sync-registered-imports.yml | Bitbucket app password (private repos only) |
GITEA_TOKEN | import-repo.yml, sync-registered-imports.yml | Gitea/Codeberg PAT (private repos only) |
ADD_MIRROR_REPO_SYNC | add-mirror-repo.yml | Scoped PAT for repo creation |
ACTIVITYSMITH_API_KEY | full-chain-flush.yml | Optional — live activity tracking; skipped if unset |
ANTHROPIC_API_KEY | validate-config.yml | Optional — 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 type | Threshold | Reset | Header |
|---|---|---|---|
| Primary (per token) | 5 000 req/hr | Top of the hour | X-RateLimit-Reset (epoch) |
| Secondary (burst/concurrency) | No fixed number — triggered by rapid sequential requests | ~60 s cooldown | X-RateLimit-Reset or Retry-After |
| Unauthenticated | 60 req/hr per IP | Top of the hour | X-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 type | Behaviour | Header |
|---|---|---|
| Per-token quota | Varies by model; gpt-4o-mini has the highest allowance | Retry-After (seconds) |
| Rate (requests/min) | Model-dependent | Retry-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 type | Threshold | Reset | Header |
|---|---|---|---|
| Authenticated REST | 2 000 req/min per token | Per-minute window | RateLimit-Reset (epoch) |
| Unauthenticated | 500 req/min per IP | Per-minute window | RateLimit-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
- Open the failed run log and search for
[rate-limit]orrate limit exceeded. - The log line includes the HTTP status, sleep duration, and attempt number.
- If all 3 retries were exhausted the next scheduled run will succeed automatically — primary limits reset hourly, secondary limits within ~60 s.
- If failures persist across multiple scheduled runs, check that
SYNC_TOKENis 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 onconclusion == 'success' - Watchdogs (
mirror-orgs-watchdog): gate onconclusion == '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:
| Subgroup | Repos | Focus |
|---|---|---|
git-management_deving | 4 | Git tooling and org management |
penguins-eggs_deving | 3 | penguins-eggs distro tools |
immutable-filesystem_deving | varies | Immutable filesystem projects |
linux-kernel_filesystem_deving | 15 | Kernel and filesystem repos |
incus_deving | varies | Incus container/VM tooling |
taubyte_deving | 8 | Taubyte protocol repos |
neon-deving | varies | KDE Neon repos |
ops | 5 | Infrastructure and tooling |
yaml-tooling_deving | 29 | YAML tools, linters, schema validators, GH Actions tooling |
cachyos_deving | 15 | CachyOS distro packages |
ai-agents_deving | 12 | AI 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 placementregistered-imports.json— upstream repos to keep in syncscripts/— 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 inscripts/is first-party automation. Do not move scripts intovendor/.
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.comfetches 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.shretries 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()inupdate-readmes.shfill_missing_sections()inupdate-readmes.shbuild_readme()increate-readmes.shgenerate_*()functions inupdate-readmes.shmerge_upstream()inscripts/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— providesbudget_init,budget_check,budget_report,osp_priority_repos, andworkflow_min_quota. The latter reads per-workflowmin_quotafromconfig/workflow-quota-costs.yml.gh-api.sh— providesgh_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 URLis a convenience GET wrapper aroundgh_apiwith 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:
| Tier | Scripts | Status |
|---|---|---|
| Full retry (canonical) | check-osp-ci.sh, cleanup-branches.sh | ✅ migrated |
| No retry, fail-fast | create-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 fail | rerun-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— providesqi_begin/qi_endfor 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 toGITHUB_STEP_SUMMARYthatupdate-quota-costs.ymlparses 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-runsandstatusesendpoints — not exposed in GraphQLactions/workflowsandactions/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:
| Pattern | Why it breaks | Fix |
|---|---|---|
VAR=" with newline before closing " | Opens an unclosed YAML flow scalar | Use printf or write to a temp file |
python3 -c " with newline before closing " | Same — unclosed flow scalar | Collapse to a single-line -c invocation |
--- on its own line | YAML document separator | Use ---- or printf '\xe2\x80\x94' for em dash |
Heredoc end-marker that is a bare YAML keyword (YAML, EOF, END) at column 0 | Parsed as a bare mapping key | Rename to OTA_CONFIG_EOF, PYEOF, etc. — anything not a YAML keyword |
Multi-line git commit -m "..." | Unclosed flow scalar | Use $'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:
| Workflow | Schedule | Purpose |
|---|---|---|
queue-manager.yml | Every 30 min + after rate-limit-rerun | Deduplicates queued runs (keeps newest per workflow) and evicts runs queued > 25 min |
quota-reserve.yml | Every 30 min + after rate-limit-rerun | Cancels 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.yml | Manual only | Fast-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:
config/workflow-priority-tiers.yml— by workflowname:field (not filename). Bothqueue-manager.shandquota-reserve.shload tiers from this file at runtime — no script edits needed.config/workflow-sync.yml— undergithub_only(most workflows) orpaired(if it has a GitLab CI counterpart).validate-workflow-guards.pywarns 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 successfully1— workflow failed or timed out2— 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_quotaper workflow)budget.shworkflow_min_quota()— pre-flight helper for self-skippingDOCS/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
| Profile | What it injects | Who should use it |
|---|---|---|
full | Everything — all workflows, scripts, config | fork-sync-all only |
mirror | Mirror/sync workflows + infra tooling | Nobody — deprecated, do not assign |
infra-core | PR automation, token rotation, token health, README render validation | Consumer repos that are targets of the mirror chain |
standalone | PR automation + token rotation only | External project forks (KDE Invent, etc.) |
upstream-sync | Upstream sync workflows | Repos 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:
- 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
- Remove them with
git rm --cachedand commit:
git rm --cached .github/workflows/add-mirror-repo.yml # etc.
git commit -m "chore: remove fork-sync-all template pollution"
- Delete the untracked files from disk:
git status --short | grep "^??" | awk '{print $2}' | xargs rm -f
- Trigger
cleanup-pollution.yml(workflow_dispatch) to clean remaining consumer repos automatically.
Repos cleaned of mirror pollution (2026-06-06)
KPort— 74 files removedbtrfs-dwarfs-framework— 133 files removed- All other
infra-coreconsumers — cleaned viacleanup-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 slug | GitLab ID |
|---|---|
git-management_deving | 130516820 |
penguins-eggs_deving | 130516402 |
immutable-filesystem_deving | 130516465 |
linux-kernel_filesystem_deving | 130516188 |
incus_deving | 130516536 |
taubyte_deving | 133909500 |
neon-deving | 130739746 |
ops | 130734009 |
yaml-tooling_deving | 133909501 |
cachyos_deving | 133909503 |
ai-agents_deving | 133909504 |
rust-systems_deving | 133954601 |
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 scratchfill— some markers present but missing sections → inject missing onesupdate— 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):
| Variable | Maps to | Used by | Notes |
|---|---|---|---|
GITLAB_TOKEN | GITLAB_TOKEN GitHub secret | Most GitLab CI jobs | api + read_repository + write_repository scope |
WORKFLOW_SECRET | SYNC_TOKEN GitHub secret | sync-forks, notify-poller, resolve-failures, rate-limit-rerun, token-health, cleanup-branches | GitHub PAT with repo + workflow + admin:org scopes |
GH_SYNC_TOKEN | GH_SYNC_TOKEN GitHub secret | sync-from-gitlab | GitHub PAT with repo + workflow scopes |
GITLAB_MAINTENANCE_TOKEN | — | maintain:storage | Inherited 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).
| Secret | PAT name | Scope | Platform / Org | Expiry | Used by | Rotate via |
|---|---|---|---|---|---|---|
SYNC_TOKEN | fork-sync-all SYNC_TOKEN | admin:org, admin:org_hook, admin:repo_hook, audit_log, delete:packages, delete_repo, gist, notifications, project, repo, workflow, write:packages | GitHub / I-D-1896 | 2026-09-02 | Most workflows | rotate-token.yml |
GH_SYNC_TOKEN | sync-mirror-watchdog | admin:org, admin:org_hook, admin:public_key, admin:repo_hook, audit_log, gist, notifications, project, repo, workflow, write:discussion, write:packages | GitHub / I-D-1896 | 2026-09-03 | mirror workflows | rotate-token.yml |
OSP_ADMIN_TOKEN | OSP_ADMIN_TOKEN | admin:org | GitHub / OpenOS-Project-OSP | 2026-09-03 | rotate-token.yml (OSP org secret rotation) | rotate-token.yml |
MIRROR_TOKEN | OSP-ORG Mirror Token | admin:enterprise, admin:gpg_key, admin:org, admin:org_hook, admin:public_key, admin:repo_hook, admin:ssh_signing_key, project, repo, workflow | GitHub / OpenOS-Project-OSP | 2026-09-01 | mirror workflows | rotate-token.yml |
ORG_MIRROR_OSP_TO_OOC | OSP-ORG Mirror Token | (same PAT as MIRROR_TOKEN) | GitHub / OpenOS-Project-OSP | 2026-09-01 | mirror-osp-to-ooc.yaml | rotate-token.yml |
ADD_MIRROR_REPO_SYNC | fork-sync-all-ona | admin:repo_hook, read:org, repo, workflow | GitHub / I-D-1896 | 2026-08-13 ⚠️ | add-mirror-repo.yml | rotate-token.yml |
GITLAB_SYNC_TOKEN | fork-sync-all-sync | api, read_repository, write_repository | GitLab / openos-project | 2027-05-13 | sync-to-gitlab.yml, mirror-osp-to-gitlab.yml, sync-from-gitlab.yml | rotate-token.yml |
GITLAB_TOKEN | Ona-Env-Secret | api | GitLab / openos-project | 2027-05-17 | Ona dev environment (injected as GITLAB_TOKEN env var); also used by gl-storage-scan, sync-to-gitlab-variant, cleanup-pollution, reconcile-org-refs | rotate-token.yml |
BITBUCKET_TOKEN | n/a (opt-in) | Bitbucket API | Bitbucket | unknown | sync-registered-imports.yml, clone-org.yml, import-repo.yml — skipped if unset | rotate-token.yml |
GITEA_TOKEN | n/a (opt-in) | Gitea API | Gitea instance | unknown | sync-registered-imports.yml, clone-org.yml, import-repo.yml — skipped if unset | rotate-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.)
- Generate a new PAT at https://github.com/settings/tokens
- Go to rotate-token.yml → Run workflow
- Select the secret name from the dropdown
- Paste the new token value into the
token_valuefield - Leave
validatechecked — it confirms the token works before finishing - 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:
- 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
- Name:
- Install the App on
OpenOS-Project-OSPorg - Note the App ID (shown on the app settings page)
- Generate a private key (PEM format) from the app settings page
- Add two repo secrets to
Interested-Deving-1896/fork-sync-all:OSP_APP_ID— the numeric App IDOSP_APP_PRIVATE_KEY— the full PEM contents (including header/footer)
- Run rotate-token.yml — it will use the App automatically
Option 2 — Dedicated PAT (bridge until App is set up)
- Generate a new PAT at https://github.com/settings/tokens with:
admin:orgscope- Authorized for
OpenOS-Project-OSPorg (SSO authorize if required)
- Add it as repo secret
OSP_ADMIN_TOKENinInterested-Deving-1896/fork-sync-all - Run rotate-token.yml — it will use
OSP_ADMIN_TOKENautomatically
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:
- Generate a new PAT with
admin:orgonOpenOS-Project-OSP - Go to OSP org secrets and update the secret value directly
- Update the expiry date in
scripts/token-monitor.sh(OSP_ORG_SECRETSarray) and in the table above
⚠️ Upcoming rotations (as of 2026-06-08):
ADD_MIRROR_REPO_SYNC— expires 2026-08-13 (66 days).token-health.ymlwill 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_sectionscase statement — must handle all 8 AI sections. If you add a new section toALL_AI_SECTIONS, add it to thecaseinfill_missing_sections,rewrite_readme, and theupdatemode loop. -
sync-registered-imports.shdoes not create repos —ensure_gh_repo()handles creation now, but the target repo must be reachable via the GitHub API. New entries inregistered-imports.jsonwill auto-create the repo on first run. -
GitLab mirror chain —
I-D-1896 → OpenOS-Project-OSP (GitHub) → openos-project (GitLab). Adding a repo togitlab-subgroups.ymlis required for GitLab mirroring. Adding toregistered-imports.jsonis required for upstream sync. Both are independent — a repo can be in one without the other. -
_inter_repo_sleepinupdate-readmes.sh— quota-aware pacing. No delay when quota > 2000; scales to 30s when < 500. The cached_quota_remainingvariable 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)
| Org | Role |
|---|---|
Interested-Deving-1896 | Primary — forks live here, all automation runs here |
OpenOS-Project-OSP | Secondary mirror — receives pushes from I-D-1896 |
OpenOS-Project-Ecosystem-OOC | Tertiary mirror — receives pushes from OSP |
gitlab.com/openos-project | GitLab 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:
sync-forks.yml(daily) — syncs all GitHub forks with their upstream parentssync-registered-imports.yml(every 6h) — re-syncs repos registered inregistered-imports.json, including non-GitHub sources (GitLab, Bitbucket, Codeberg, etc.)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:
| Subgroup | Topic |
|---|---|
git-management_deving | Git tooling |
penguins-eggs_deving | penguins-eggs ecosystem |
immutable-filesystem_deving | Immutable Linux |
linux-kernel_filesystem_deving | Kernel and filesystem |
incus_deving | Incus / container infrastructure |
taubyte_deving | Taubyte platform |
neon-deving | KDE Neon ecosystem |
ops | Operations and control plane |
yaml-tooling_deving | YAML, CI, and tooling |
cachyos_deving | CachyOS packages |
ai-agents_deving | AI agent tooling |
rust-systems_deving | Rust 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)
| Layer | Threshold | Action |
|---|---|---|
quota-reserve | < 1000 remaining | Cancels tier-4 (LOW) queued runs |
quota-reserve | < 500 remaining | Cancels tier-3 (MEDIUM) queued runs |
queue-manager | Run queued > 25 min | Evicts stale queued runs |
queue-manager | Duplicate workflow | Keeps 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
| File | Purpose |
|---|---|
config/gitlab-subgroups.yml | GitLab subgroup placement for ~157 repos |
config/workflow-priority-tiers.yml | Priority tier for each workflow (used by queue-manager and quota-reserve) |
config/workflow-sync.yml | GitHub ↔ GitLab CI job mapping (used by validate-workflow-guards) |
config/workflow-cost-profiles.yml | Estimated API cost per workflow run |
config/ota-registry.yml | Repos opted in to the OTA update system |
config/ota-blocklist.yml | Orgs/namespaces excluded from OTA by default |
config/template-manifest.yml | Template sync profiles and file ownership |
config/template-consumers.yml | Repos consuming each template profile |
registered-imports.json | Upstream 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:
| Component | Description |
|---|---|
vendor/infra-dashboard | Mirror-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:
| Secret | Used by | Scope |
|---|---|---|
SYNC_TOKEN | Most workflows | repo, workflow, admin:org |
GH_TOKEN | Validation, README, config workflows | repo, 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.ymlandconfig/workflow-priority-tiers.yml.
Quota cost columns: Low = fast/cached run · Mid = typical (p50) · High = large/uncached (p95)
Tier 1 — Critical
| Workflow | Synopsis | Schedule | min_quota | Low | Mid | High |
|---|---|---|---|---|---|---|
| Cancel Runs After Token Rotation | Cancels queued and in-progress runs that hold the old (now-invalid) token immediately after Rotate Secret Token completes, preventing Bad credentials failures. | Manual | 50 | 5 | 20 | 50 |
| Cancel Stale Runs | Cancels 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. | Manual | 100 | 10 | 30 | 80 |
| Critical Deploy | Fast-lane workflow for deploying critical fixes when the system is degraded — commits and pushes changes, clears the queue aggressively, then dispatches priority workflows. | Manual | 50 | 5 | 30 | 100 |
| Mirror Watchdog | Triggers when any mirror workflow fails — waits 5 minutes then retries once. Surfaces persistent failures in the Actions tab without consuming quota on repeated retries. | 50 | 5 | 15 | 30 | |
| Pre-Flush Prep | Prepares 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. | Manual | 100 | 10 | 30 | 60 |
| Queue Manager | Deduplicates 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 min | 50 | 5 | 15 | 30 |
| Quota Monitor | Polls GitHub quota and optionally dispatches a target workflow once quota recovers above a configurable threshold. Dispatch-only — never scheduled. | 10 | 1 | 5 | 10 | |
| Quota Reserve | Cancels 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 min | 10 | 1 | 5 | 15 |
| Rate-Limit Re-trigger | Scans recently-failed workflow runs, identifies those that failed due to rate limiting, and re-triggers them after their quota reset epoch. | Every 4h at :05 | 50 | 5 | 20 | 50 |
| Rotate Secret Token | Rotates 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. | Manual | 50 | 5 | 10 | 20 |
| Token Health Monitor | Checks 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 UTC | 50 | 5 | 10 | 20 |
| Validate Config | Validates 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. | Manual | 50 | 2 | 5 | 10 |
Tier 2 — High
| Workflow | Synopsis | Schedule | min_quota | Low | Mid | High |
|---|---|---|---|---|---|---|
| Add Mirror Repo | Adds 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. | Manual | 200 | 10 | 30 | 60 |
| Full Chain Flush | Orchestrates 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 * * | 1000 | 100 | 400 | 1000 |
| Import Repository | Platform-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. | 100 | 10 | 30 | 60 | |
| Merge Repos into Monorepo | Merges multiple git repositories into a single monorepo, preserving full commit history, tags, and Git LFS objects. Manual dispatch only. | Manual | 100 | 10 | 30 | 60 |
| Mirror Interested-Deving-1896 → OSP | Bare-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 :13 | 500 | 20 | 80 | 200 |
| Mirror OSP → GitLab | Mirrors 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 :23 | 300 | 5 | 20 | 50 |
| Mirror to OpenOS-Project-Ecosystem-OOC | Bare-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 :15 | 300 | 20 | 80 | 200 |
| Pre-Mirror CI Gate | Checks 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. | Manual | 800 | 50 | 150 | 300 |
| Rebase PRs | Rebases open PRs in Interested-Deving-1896 onto their base branch when they fall behind, keeping PRs mergeable without manual intervention. | Daily 05:10 UTC | 100 | 5 | 20 | 50 |
| Sync All Forks | Syncs 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 UTC | 500 | 50 | 200 | 500 |
| Sync Registered Imports | Re-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 :55 | 200 | 5 | 15 | 30 |
| Sync from GitLab | Pulls changes from the GitLab mirror back to GitHub when GitLab CI has committed changes (e.g. generated files, CI artifacts). | Daily 04:22 UTC | 100 | 5 | 20 | 50 |
| Sync to GitLab Variant | Variant 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 :50 | 100 | 5 | 20 | 50 |
Tier 3 — Medium
| Workflow | Synopsis | Schedule | min_quota | Low | Mid | High |
|---|---|---|---|---|---|---|
| Check OSP-Bound CI Status | Checks CI status across all OSP-bound repos and reports failing workflows. Triggers resolve-failures when failures are detected. | Daily 09:05 UTC | 300 | 50 | 150 | 300 |
| Cleanup Stale Branches | Deletes branches that have been merged into the default branch across all repos in Interested-Deving-1896, OSP, and OOC. | 29 4 1 * * | 200 | 10 | 60 | 200 |
| Cleanup Template Pollution | Removes files incorrectly propagated from fork-sync-all to consumer repos via the template sync pipeline, across all three GitHub orgs and GitLab. | Manual | 200 | 20 | 80 | 200 |
| Clone Org | Clones all repositories from an org or user on any supported platform (GitHub, GitLab, Bitbucket, Gitea) into Interested-Deving-1896. | Manual | 200 | 20 | 80 | 200 |
| Create Missing READMEs | Creates README.md from the standard template for OSP-bound repos that have no README, with placeholder sections for human-owned content. | Daily 07:08 UTC | 200 | 20 | 80 | 200 |
| Deploy Book | Builds the mdBook documentation site from DOCS/ and deploys it to GitHub Pages at interested-deving-1896.github.io/fork-sync-all/. | Manual | 50 | 5 | 10 | 20 |
| Docker → Incus Migration | Scans 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 UTC | 100 | 10 | 40 | 100 |
| Enforce Agnostic Vendor | Scans vendor/ for distro-specific hardcoded fallback values in shell, YAML, and TypeScript. All vendored components must be deployment-agnostic. | Manual | 50 | 2 | 5 | 10 |
| Fork KDE Neon Repos | One-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. | Manual | 100 | 10 | 30 | 60 |
| Generate Book Pages | Regenerates DOCS/generated/ pages from config sources (workflow-quota-costs.yml, priority-tiers.yml, gitlab-subgroups.yml, registered-imports.json) and commits the result. | Manual | 50 | 1 | 2 | 5 |
| Inject Built-with-Ona Badges | Adds 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 UTC | 200 | 5 | 30 | 80 |
| OTA Discover | Scans 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 UTC | 100 | 10 | 40 | 100 |
| OTA Opt-In | Propagated 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. | 50 | 5 | 15 | 30 | |
| OTA Release | Triggered 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. | Manual | 100 | 10 | 40 | 100 |
| OTA Self-Update | Propagated 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 UTC | 50 | 5 | 15 | 30 |
| PR Automation | Applies 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 push | 50 | 5 | 15 | 30 |
| Pipeline Telemetry | Post-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. | Manual | 200 | 5 | 15 | 30 |
| Post-Flush Verification | End-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. | Manual | 300 | 150 | 350 | 600 |
| 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. | Manual | 50 | 5 | 15 | 30 |
| Reconcile Org References | Rewrites 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 UTC | 300 | 10 | 60 | 150 |
| Resolve CI Failures | Analyses CI failure patterns across OSP-bound repos and applies automated fixes (dependency updates, config corrections, workflow patches) where possible. | Daily 07:43 UTC | 100 | 10 | 40 | 100 |
| Setup Dashboard Variables | Sets all VITE_* repository variables required by the infra-dashboard public-dashboard build. Safe to re-run — blank inputs leave existing variables unchanged. | Manual | 50 | 5 | 15 | 30 |
| Setup OSP Mirror Workflows | Ensures all repos in OpenOS-Project-OSP have the correct mirror workflow files and secrets configured for the OSP→OOC mirror chain. | Every 6h at :45 | 200 | 20 | 80 | 200 |
| Sync Registry Sources | Registry-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 UTC | 100 | 10 | 40 | 100 |
| Sync Template | Syncs 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). | Manual | 200 | 20 | 80 | 200 |
| Sync Upstream Sources | Reads 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 UTC | 200 | 20 | 80 | 200 |
| Sync btrfs-devel Branches | Syncs tracked btrfs-devel branches from the upstream kernel tree into the btrfs-dwarfs-framework fork. | Every 6h at :02 | 100 | 5 | 20 | 50 |
| Sync pieroproietti Forks | Syncs all penguins-eggs forks owned by Interested-Deving-1896 with their upstream pieroproietti sources via merge-upstream. | Every 8h at :07 | 100 | 10 | 40 | 100 |
| Sync to GitLab | Pushes the Interested-Deving-1896/fork-sync-all repo to its GitLab mirror at openos-project/ops/fork-sync-all. | Daily 09:17 UTC | 100 | 5 | 20 | 50 |
| Update READMEs | Regenerates 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 UTC | 300 | 50 | 150 | 300 |
| Validate README Render | Checks README.md for rendering issues — leaked log lines, unclosed fences, bare brackets, raw angle brackets, unclosed AI markers, missing H1, and empty sections. | Manual | 50 | 5 | 15 | 30 |
| Verify Mirror Integrity | Compares 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. | Manual | 400 | 50 | 100 | 150 |
Tier 4 — Low
| Workflow | Synopsis | Schedule | min_quota | Low | Mid | High |
|---|---|---|---|---|---|---|
| Check GitLab CI Sync | Compares 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. | Manual | 50 | 2 | 5 | 10 |
| Generate NotebookLM Content | Generates NotebookLM content artifacts (audio, video, slides, infographic, quiz, flashcards, report) for a given notebook and uploads them to a GitHub Release. | Manual | 50 | 5 | 15 | 30 |
| Generate OSP Dependency Graph | Scans all OSP-bound repos for package.json and requirements.txt files and generates a dependency graph showing inter-repo relationships. | Weekly Sun 03:10 UTC | 100 | 20 | 60 | 150 |
| GitLab Storage Scan | Scans all projects under openos-project on GitLab and reports storage usage. Useful for diagnosing when the namespace approaches its 10 GiB limit. | Manual | 50 | 2 | 5 | 10 |
| LTS README Standardisation | Standardises README.md structure for LTS-tagged repos, ensuring they follow the LTS template with correct version badges and support tables. | 19 3 1 * * | 100 | 10 | 40 | 100 |
| List Chromium GitLab Repos | Lists all projects under the Chromium_Browser_OS_Deving GitLab group. Informational only — used to audit what has been mirrored. | Manual | 50 | 2 | 5 | 10 |
| Mirror Artifacts | Mirrors GitHub Releases, Flatpak packages, and RPM packages from Interested-Deving-1896 repos to their OSP and OOC counterparts. | Every 8h at :10 | 200 | 10 | 50 | 150 |
| Mirror Orgs | Mirrors all repos from Interested-Deving-1896 to OpenOS-Project-OSP and OpenOS-Project-Ecosystem-OOC using bare clone + push --mirror. | Daily 02:17 UTC | 100 | 20 | 60 | 150 |
| Mirror Releases | Mirrors GitHub Releases (tags + release notes + assets) from Interested-Deving-1896 repos to their OSP and OOC counterparts. | Every 6h at :03 | 200 | 10 | 50 | 150 |
| Notification Poller | Polls GitHub notifications for unread CI failure notifications and triggers resolve-failures immediately when any are found. | Every 4h at :32 | 50 | 1 | 5 | 15 |
| README Wizard | AI-guided README authoring — writes or rewrites a README for a specific repo according to custom instructions (audience, tone, sections), respecting existing human-owned markers. | Manual | 100 | 10 | 30 | 60 |
| Rate Limit Status | Queries current rate limit status for GitHub REST, GitLab, and GitHub Models APIs. Exits non-zero if any platform is below 10% remaining. | Manual | 10 | 2 | 5 | 10 |
| Refresh NotebookLM Auth | Rotates 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 UTC | 10 | 1 | 2 | 5 |
| Repo Manifest | Exports a manifest of all repos in an org, or imports repos from a manifest into a target GitHub org. Supports multi-platform bulk import. | Manual | 100 | 10 | 40 | 100 |
| Setup GitLab CI Schedules | Replaces all existing GitLab pipeline schedules in openos-project/ops/fork-sync-all with the 3 consolidated CADENCE-based schedules. Safe to re-run. | 50 | 2 | 5 | 10 | |
| Shallow Reclone Large GitLab Mirrors | Reduces 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. | Manual | 50 | 2 | 5 | 10 |
| Sync penguins-eggs docs to penguins-eggs-book | Triggered by repository_dispatch from penguins-eggs when docs/chromiumos/ changes on all-features. Syncs the updated docs into the penguins-eggs-book repo. | Manual | 50 | 5 | 15 | 30 |
| Translate Docs | Translates DOCS/ mdBook pages into a target language using GitHub Models API. Writes translated files to DOCS/ | Daily 11:15 UTC | 100 | 5 | 15 | 30 |
| Translate READMEs | Translates README.md files for OSP-bound repos into additional languages using GitHub Models API. Writes translated files alongside the English original. | Daily 10:43 UTC | 100 | 10 | 40 | 100 |
| Trigger Artifact Mirror | Dispatches 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. | 50 | 2 | 5 | 10 | |
| Update Infrastructure Dependencies | Runs Dependabot-style updates for GitHub Actions versions across all workflow files and opens a grouped PR. | Weekly Mon 06:11 UTC | 50 | 5 | 15 | 30 |
| Update Quota Cost Registry | Reads 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 UTC | 200 | 30 | 80 | 150 |
| Update Workflow Triggers Doc | Regenerates docs/workflow-triggers.md and docs/workflow-triggers.txt whenever a workflow file changes on main. Commits the result directly to main. | Manual | 50 | 5 | 10 | 20 |
| Upstream Direct Commits from OSP + OOC | Detects commits pushed directly to OSP/OOC default branches (without a PR) and opens PRs against Interested-Deving-1896 to reconcile them. | Every 6h at :47 | 200 | 20 | 80 | 200 |
| Upstream PRs from OSP + OOC | Detects 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 :33 | 200 | 20 | 80 | 200 |
| Upstream Workflow Proposal | Scans 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 UTC | 50 | 5 | 20 | 50 |
✦ 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/andconfig/workflow-quota-costs.yml
Mirror Chain
| Workflow | Synopsis | File | Schedule | Also triggers on |
|---|---|---|---|---|
| Mirror Artifacts ↗ ▶ Run | Mirrors GitHub Releases, Flatpak packages, and RPM packages from Interested-Deving-1896 repos to their OSP and OOC counterparts. | mirror-artifacts.yml | Every 8h at :10 | dispatch |
| Mirror Orgs ↗ ▶ Run | Mirrors all repos from Interested-Deving-1896 to OpenOS-Project-OSP and OpenOS-Project-Ecosystem-OOC using bare clone + push --mirror. | mirror-orgs-full.yml | Daily 02:17 | dispatch |
| Mirror Watchdog ↗ ▶ Run | Triggers 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.yml | — | Mirror Interested-Deving-1896 → OSP completes · Mirror Orgs completes · Mirror OSP → GitLab completes · Mirror Releases completes · Mirror Artifacts completes |
| Mirror OSP → GitLab ↗ ▶ Run | Mirrors 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.yml | Every 8h at :23 | Add Mirror Repo completes · dispatch |
| Mirror to OpenOS-Project-Ecosystem-OOC ↗ ▶ Run | Bare-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.yaml | Every 6h at :15 | dispatch |
| Mirror Releases ↗ ▶ Run | Mirrors GitHub Releases (tags + release notes + assets) from Interested-Deving-1896 repos to their OSP and OOC counterparts. | mirror-releases.yml | Every 6h at :03 | dispatch |
| Mirror Interested-Deving-1896 → OSP ↗ ▶ Run | Bare-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.yml | Every 6h at :13 | dispatch |
OSP-Bound Repo Management
| Workflow | Synopsis | File | Schedule | Also triggers on |
|---|---|---|---|---|
| Add Mirror Repo ↗ ▶ Run | Adds 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.yml | — | dispatch |
| Check OSP-Bound CI Status ↗ ▶ Run | Checks CI status across all OSP-bound repos and reports failing workflows. Triggers resolve-failures when failures are detected. | check-osp-ci.yml | Daily 09:05 | Mirror Interested-Deving-1896 → OSP completes · Add Mirror Repo completes · dispatch |
| Setup OSP Mirror Workflows ↗ ▶ Run | Ensures all repos in OpenOS-Project-OSP have the correct mirror workflow files and secrets configured for the OSP→OOC mirror chain. | setup-osp-mirrors.yml | Every 6h at :45 | dispatch |
Fork & Import Sync
| Workflow | Synopsis | File | Schedule | Also triggers on |
|---|---|---|---|---|
| Import Repository ↗ ▶ Run | Platform-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.yml | — | dispatch |
| Sync btrfs-devel Branches ↗ ▶ Run | Syncs tracked btrfs-devel branches from the upstream kernel tree into the btrfs-dwarfs-framework fork. | sync-btrfs-devel-branches.yml | Every 6h at :02 | dispatch |
| Sync All Forks ↗ ▶ Run | Syncs 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.yml | Daily 06:07 | dispatch |
| Sync from GitLab ↗ ▶ Run | Pulls changes from the GitLab mirror back to GitHub when GitLab CI has committed changes (e.g. generated files, CI artifacts). | sync-from-gitlab.yml | Daily 04:22 | dispatch |
| Sync pieroproietti Forks ↗ ▶ Run | Syncs all penguins-eggs forks owned by Interested-Deving-1896 with their upstream pieroproietti sources via merge-upstream. | sync-pieroproietti-forks.yml | Every 8h at :07 | dispatch |
| Sync Registered Imports ↗ ▶ Run | Re-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.yml | Every 6h at :55 | dispatch |
| Sync Registry Sources ↗ ▶ Run | Registry-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.yml | Daily 03:05 | dispatch |
| Sync Upstream Sources ↗ ▶ Run | Reads 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.yml | Daily 01:37 | dispatch |
| Upstream Direct Commits from OSP + OOC ↗ ▶ Run | Detects commits pushed directly to OSP/OOC default branches (without a PR) and opens PRs against Interested-Deving-1896 to reconcile them. | upstream-commits.yml | Every 6h at :47 | dispatch |
| Upstream PRs from OSP + OOC ↗ ▶ Run | Detects commits on OSP and OOC default branches that are not present in Interested-Deving-1896 and opens PRs to bring them upstream. | upstream-prs.yml | Every 6h at :33 | dispatch |
GitLab Sync
| Workflow | Synopsis | File | Schedule | Also triggers on |
|---|---|---|---|---|
| Sync to GitLab Variant ↗ ▶ Run | Variant 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.yml | Every 8h at :50 | push to config/ota-registry.yml, config/ota-blocklist.yml, .ota/schema.yml (+2 more) · dispatch |
| Sync to GitLab ↗ ▶ Run | Pushes the Interested-Deving-1896/fork-sync-all repo to its GitLab mirror at openos-project/ops/fork-sync-all. | sync-to-gitlab.yml | Daily 09:17 | dispatch |
README Management
| Workflow | Synopsis | File | Schedule | Also triggers on |
|---|---|---|---|---|
| Create Missing READMEs ↗ ▶ Run | Creates README.md from the standard template for OSP-bound repos that have no README, with placeholder sections for human-owned content. | create-readmes.yml | Daily 07:08 | Add Mirror Repo completes · Import Repository completes · Clone Org completes · Merge Repos into Monorepo completes · dispatch |
| Inject Built-with-Ona Badges ↗ ▶ Run | Adds 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.yml | Daily 08:15 | Mirror OSP → GitLab completes · dispatch |
| LTS README Standardisation ↗ ▶ Run | Standardises README.md structure for LTS-tagged repos, ensuring they follow the LTS template with correct version badges and support tables. | lts-readmes.yml | Monthly 1st 03:19 | Rebuild LTS Branch (penguins-eggs) completes · dispatch |
| README Wizard ↗ ▶ Run | AI-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.yml | — | dispatch |
| Translate READMEs ↗ ▶ Run | Translates README.md files for OSP-bound repos into additional languages using GitHub Models API. Writes translated files alongside the English original. | translate-readmes.yml | Daily 10:43 | Update 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 ↗ ▶ Run | Regenerates 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.yml | Daily 03:15 | push to config/gitlab-subgroups.yml, config/template-manifest.yml · Sync Registered Imports completes · dispatch |
| Validate README Render ↗ ▶ Run | Checks 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.yml | — | push to README.md · Update READMEs completes · dispatch |
CI & Failure Resolution
| Workflow | Synopsis | File | Schedule | Also triggers on |
|---|---|---|---|---|
| Check GitLab CI Sync ↗ ▶ Run | Compares 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.yml | — | dispatch |
| Notification Poller ↗ ▶ Run | Polls GitHub notifications for unread CI failure notifications and triggers resolve-failures immediately when any are found. | notify-poller.yml | Every 4h at :32 | dispatch |
| PR Automation ↗ ▶ Run | Applies 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.yml | — | pull_request |
| Rate-Limit Re-trigger ↗ ▶ Run | Scans recently-failed workflow runs, identifies those that failed due to rate limiting, and re-triggers them after their quota reset epoch. | rate-limit-rerun.yml | Every 4h at :05 | dispatch |
| Rate Limit Status ↗ ▶ Run | Queries 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.yml | — | dispatch |
| Rebuild LTS Branch (penguins-eggs) ↗ ▶ Run | Rebases the all-features branch onto the upstream master after each pieroproietti sync, then force-pushes the result to the lts branch. | rebase-lts.yml | — | Sync pieroproietti Forks completes · dispatch |
| Resolve CI Failures ↗ ▶ Run | Analyses CI failure patterns across OSP-bound repos and applies automated fixes (dependency updates, config corrections, workflow patches) where possible. | resolve-failures.yml | Daily 07:43 | dispatch |
Maintenance & Housekeeping
| Workflow | Synopsis | File | Schedule | Also triggers on |
|---|---|---|---|---|
| Cancel Runs After Token Rotation ↗ ▶ Run | Cancels 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.yml | — | Rotate Secret Token completes |
| Cleanup Stale Branches ↗ ▶ Run | Deletes branches that have been merged into the default branch across all repos in Interested-Deving-1896, OSP, and OOC. | cleanup-branches.yml | Monthly 1st 04:29 | dispatch |
| Cleanup Template Pollution ↗ ▶ Run | Removes files incorrectly propagated from fork-sync-all to consumer repos via the template sync pipeline, across all three GitHub orgs and GitLab. | cleanup-pollution.yml | — | dispatch |
| Generate OSP Dependency Graph ↗ ▶ Run | Scans all OSP-bound repos for package.json and requirements.txt files and generates a dependency graph showing inter-repo relationships. | generate-dep-graph.yml | Weekly Sun 03:10 | dispatch |
| Reconcile Org References ↗ ▶ Run | Rewrites 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.yml | Daily 05:50 | dispatch |
| Rotate Secret Token ↗ ▶ Run | Rotates 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.yml | — | dispatch |
| Sync Template ↗ ▶ Run | Syncs 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.yml | — | push to .devcontainer/**, .ona/**, config/template-manifest.yml · dispatch |
| Token Health Monitor ↗ ▶ Run | Checks 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.yml | Weekly Mon 09:24 | dispatch |
| Update Infrastructure Dependencies ↗ ▶ Run | Runs Dependabot-style updates for GitHub Actions versions across all workflow files and opens a grouped PR. | update-infra-deps.yml | Weekly Mon 06:11 | dispatch |
| Update Workflow Triggers Doc ↗ ▶ Run | Regenerates 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.yml | — | push to .github/workflows/**, config/workflow-quota-costs.yml · dispatch |
| Upstream Workflow Proposal ↗ ▶ Run | Scans 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.yml | Weekly Mon 06:06 | dispatch |
| Validate Config ↗ ▶ Run | Validates 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.yml | — | push to config/gitlab-subgroups.yml, config/workflow-sync.yml, config/workflow-cost-profiles.yml (+16 more) · pull_request · dispatch |
Full Pipeline
| Workflow | Synopsis | File | Schedule | Also triggers on |
|---|---|---|---|---|
| Critical Deploy ↗ ▶ Run | Fast-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.yml | — | dispatch |
| Full Chain Flush ↗ ▶ Run | Orchestrates 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.yml | Monthly 1st 05:17 | dispatch |
| Pre-Flush Prep ↗ ▶ Run | Prepares 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.yml | — | dispatch |
Utility / On-Demand
| Workflow | Synopsis | File | Trigger |
|---|---|---|---|
| Cancel Stale Runs ↗ ▶ Run | Cancels 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.yml | dispatch |
| Clone Org ↗ ▶ Run | Clones all repositories from an org or user on any supported platform (GitHub, GitLab, Bitbucket, Gitea) into Interested-Deving-1896. | clone-org.yml | dispatch |
| Deploy Book ↗ ▶ Run | Builds the mdBook documentation site from DOCS/ and deploys it to GitHub Pages at interested-deving-1896.github.io/fork-sync-all/. | deploy-book.yml | push to DOCS/**, book.toml, README.md (+2 more) · dispatch |
| Docker → Incus Migration ↗ ▶ Run | Scans repos for Docker artifacts (Dockerfile, docker-compose.yml) and replaces them with Incus equivalents. Runs after Add Mirror Repo and weekly. | docker-to-incus.yml | Add Mirror Repo completes · dispatch |
| Enforce Agnostic Vendor ↗ ▶ Run | Scans vendor/ for distro-specific hardcoded fallback values in shell, YAML, and TypeScript. All vendored components must be deployment-agnostic. | enforce-agnostic-vendor.yml | push to vendor/**, scripts/check-vendor-agnostic.sh, .github/workflows/enforce-agnostic-vendor.yml · pull_request · dispatch |
| Fork KDE Neon Repos ↗ ▶ Run | One-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.yml | dispatch |
| Generate Book Pages ↗ ▶ Run | Regenerates 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.yml | push to config/workflow-quota-costs.yml, config/workflow-priority-tiers.yml, config/gitlab-subgroups.yml (+2 more) · dispatch |
| Generate NotebookLM Content ↗ ▶ Run | Generates NotebookLM content artifacts (audio, video, slides, infographic, quiz, flashcards, report) for a given notebook and uploads them to a GitHub Release. | generate-notebooklm.yml | dispatch |
| GitLab Storage Scan ↗ ▶ Run | Scans 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.yml | dispatch |
| List Chromium GitLab Repos ↗ ▶ Run | Lists all projects under the Chromium_Browser_OS_Deving GitLab group. Informational only — used to audit what has been mirrored. | list-chromium-repos.yml | dispatch |
| Merge Repos into Monorepo ↗ ▶ Run | Merges multiple git repositories into a single monorepo, preserving full commit history, tags, and Git LFS objects. Manual dispatch only. | merge-to-monorepo.yml | dispatch |
| OTA Discover ↗ ▶ Run | Scans forks of fork-sync-all for .ota/config.yml with enabled: true and adds newly discovered repos to config/ota-registry.yml. | ota-discover.yml | dispatch |
| OTA Opt-In ↗ ▶ Run | Propagated 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.yml | dispatch |
| OTA Release ↗ ▶ Run | Triggered 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.yml | push to (any) · dispatch |
| OTA Self-Update ↗ ▶ Run | Propagated 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.yml | dispatch |
| Pipeline Telemetry ↗ ▶ Run | Post-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.yml | Full 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 ↗ ▶ Run | End-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.yml | dispatch |
| Pre-Mirror CI Gate ↗ ▶ Run | Checks 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.yml | dispatch |
| Queue Manager ↗ ▶ Run | Deduplicates 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.yml | dispatch |
| Quota Monitor ↗ ▶ Run | Polls GitHub quota and optionally dispatches a target workflow once quota recovers above a configurable threshold. Dispatch-only — never scheduled. | quota-monitor.yml | dispatch |
| Quota Reserve ↗ ▶ Run | Cancels 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.yml | dispatch |
| Rebase PRs ↗ ▶ Run | Rebases open PRs in Interested-Deving-1896 onto their base branch when they fall behind, keeping PRs mergeable without manual intervention. | rebase-prs.yml | Validate Config completes · dispatch |
| Refresh NotebookLM Auth ↗ ▶ Run | Rotates the short-lived __Secure-1PSIDTS cookie in NOTEBOOKLM_AUTH_JSON weekly and writes the updated state back to the repo secret. | refresh-notebooklm-auth.yml | dispatch |
| Repo Manifest ↗ ▶ Run | Exports 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.yml | dispatch |
| Setup Dashboard Variables ↗ ▶ Run | Sets 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.yml | dispatch |
| Setup GitLab CI Schedules ↗ ▶ Run | Replaces 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.yml | dispatch |
| Shallow Reclone Large GitLab Mirrors ↗ ▶ Run | Reduces 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.yml | dispatch |
| Sync penguins-eggs docs to penguins-eggs-book ↗ ▶ Run | Triggered 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.yml | dispatch |
| Translate Docs ↗ ▶ Run | Translates DOCS/ mdBook pages into a target language using GitHub Models API. Writes translated files to DOCS/ | translate-docs.yml | Deploy Book completes · dispatch |
| Trigger Artifact Mirror ↗ ▶ Run | Dispatches 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 ↗ ▶ Run | Reads 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.yml | dispatch |
| Verify Mirror Integrity ↗ ▶ Run | Compares 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.yml | dispatch |
Schedule Summary (UTC)
| Time | Frequency | Workflow |
|---|---|---|
| 01:37 | Daily | Sync Upstream Sources ↗ ▶ Run |
| 02:17 | Daily | Mirror Orgs ↗ ▶ Run |
| 03:05 | Daily | Sync Registry Sources ↗ ▶ Run |
| Sun 03:08 | Weekly | Docker → Incus Migration ↗ ▶ Run |
| Sun 03:10 | Weekly | Generate OSP Dependency Graph ↗ ▶ Run |
| 03:15 | Daily | Update READMEs ↗ ▶ Run |
| 1st 03:19 | Monthly | LTS README Standardisation ↗ ▶ Run |
| at :05 | Every 4h | Rate-Limit Re-trigger ↗ ▶ Run |
| 04:22 | Daily | Sync from GitLab ↗ ▶ Run |
| 1st 04:29 | Monthly | Cleanup Stale Branches ↗ ▶ Run |
| at :32 | Every 4h | Notification Poller ↗ ▶ Run |
| 05:10 | Daily | Rebase PRs ↗ ▶ Run |
| Mon 05:15 | Weekly | OTA Self-Update ↗ ▶ Run |
| 1st 05:17 | Monthly | Full Chain Flush ↗ ▶ Run |
| 05:50 | Daily | Reconcile Org References ↗ ▶ Run |
| at :02 | Every 6h | Sync btrfs-devel Branches ↗ ▶ Run |
| at :03 | Every 6h | Mirror Releases ↗ ▶ Run |
| Mon 06:06 | Weekly | Upstream Workflow Proposal ↗ ▶ Run |
| 06:07 | Daily | Sync All Forks ↗ ▶ Run |
| Mon 06:11 | Weekly | Update Infrastructure Dependencies ↗ ▶ Run |
| at :13 | Every 6h | Mirror Interested-Deving-1896 → OSP ↗ ▶ Run |
| at :15 | Every 6h | Mirror to OpenOS-Project-Ecosystem-OOC ↗ ▶ Run |
| Tue 06:17 | Weekly | Refresh NotebookLM Auth ↗ ▶ Run |
| at :33 | Every 6h | Upstream PRs from OSP + OOC ↗ ▶ Run |
| 06:38 | Daily | OTA Discover ↗ ▶ Run |
| at :45 | Every 6h | Setup OSP Mirror Workflows ↗ ▶ Run |
| at :47 | Every 6h | Upstream Direct Commits from OSP + OOC ↗ ▶ Run |
| at :55 | Every 6h | Sync Registered Imports ↗ ▶ Run |
| 07:08 | Daily | Create Missing READMEs ↗ ▶ Run |
| 07:43 | Daily | Resolve CI Failures ↗ ▶ Run |
| Mon 08:00 | Weekly | Update Quota Cost Registry ↗ ▶ Run |
| at :07 | Every 8h | Sync pieroproietti Forks ↗ ▶ Run |
| at :10 | Every 8h | Mirror Artifacts ↗ ▶ Run |
| 08:15 | Daily | Inject Built-with-Ona Badges ↗ ▶ Run |
| at :23 | Every 8h | Mirror OSP → GitLab ↗ ▶ Run |
| at :50 | Every 8h | Sync to GitLab Variant ↗ ▶ Run |
| 09:05 | Daily | Check OSP-Bound CI Status ↗ ▶ Run |
| 09:17 | Daily | Sync to GitLab ↗ ▶ Run |
| Mon 09:24 | Weekly | Token Health Monitor ↗ ▶ Run |
| 10:43 | Daily | Translate READMEs ↗ ▶ Run |
| 11:15 | Daily | Translate Docs ↗ ▶ Run |
| Every 30 min | Queue Manager ↗ ▶ Run | |
| Every 30 min | Quota 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
Upstream — Interested-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.
Registry — config/ota-registry.yml. The list of all opted-in repos.
Blocklist — config/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:
- Creates
.ota/config.ymlin the fork with sensible defaults - Opens a registration PR against
fork-sync-all/config/ota-registry.yml
Inputs:
| Input | Description | Default |
|---|---|---|
upstream_override | Override the upstream source (for fork-of-fork cases) | (auto-detected) |
mirror_chain_opt_in | Set true if the fork is in the mirror chain | false |
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:
| Input | Description | Default |
|---|---|---|
dry_run | Report new opt-ins without updating registry or opening PR | false |
ota-release.yml — triggered on semver tag push
Triggered when a tag matching v*.*.* is pushed to fork-sync-all. It:
- Iterates all repos in
config/ota-registry.yml(skippingdisabled: true) - For each repo, calls
ota-payload-build.shto assemble the diff - Opens a PR in the fork with the payload
- Updates
CHANGELOG.mdinfork-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:
- Checks the latest OTA release tag from
fork-sync-all - Compares against the fork's
pinned_shain.ota/config.yml - If behind, applies the payload and updates
pinned_shaandpinned_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:
- Detects the fork's upstream parent via GitHub API (or uses
upstream_override) - Diffs the fork's current state at
pinned_shaagainst the latest upstream tag - 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 viaworkflow_overrides.claim
- Files listed in the fork's
- Applies
workflow_overrides.disclaimto 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:
- Create
.ota/config.ymlin the fork (copy from.ota/schema.yml, setenabled: trueandrepo) - 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
- Open a PR against
fork-sync-all—validate-config.ymlwill 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.yml — ota-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)
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.
| Subgroup | GitLab ID | Repos | GitLab URL |
|---|---|---|---|
git-management_deving | 130516820 | 1 | https://gitlab.com/openos-project/git-management_deving |
penguins-eggs_deving | 130516402 | 17 | https://gitlab.com/openos-project/penguins-eggs_deving |
immutable-filesystem_deving | 130516465 | 1 | https://gitlab.com/openos-project/immutable-filesystem_deving |
linux-kernel_filesystem_deving | 130516188 | 14 | https://gitlab.com/openos-project/linux-kernel_filesystem_deving |
incus_deving | 130516536 | 49 | https://gitlab.com/openos-project/incus_deving |
taubyte_deving | 133909500 | 1 | https://gitlab.com/openos-project/taubyte_deving |
neon-deving | 130739746 | 8 | https://gitlab.com/openos-project/neon-deving |
ops | 130734009 | 9 | https://gitlab.com/openos-project/ops |
yaml-tooling_deving | 133909501 | 34 | https://gitlab.com/openos-project/yaml-tooling_deving |
cachyos_deving | 133909503 | 12 | https://gitlab.com/openos-project/cachyos_deving |
ai-agents_deving | 133909504 | 10 | https://gitlab.com/openos-project/ai-agents_deving |
rust-systems_deving | 133954601 | 2 | https://gitlab.com/openos-project/rust-systems_deving |
git-management_deving
penguins-eggs_deving
| Repo | GitHub | GitLab |
|---|---|---|
eggs-ai | GitHub | GitLab |
eggs-gui | GitHub | GitLab |
oa-tools | GitHub | GitLab |
penguins-distrobuilder | GitHub | GitLab |
penguins-eggs | GitHub | GitLab |
penguins-eggs-audit | GitHub | GitLab |
penguins-eggs-book | GitHub | GitLab |
penguins-eggs-integrations | GitHub | GitLab |
penguins-eggs-prefix | GitHub | GitLab |
penguins-eggs-stage3 | GitHub | GitLab |
penguins-immutable-framework | GitHub | GitLab |
penguins-incus-platform | GitHub | GitLab |
penguins-kernel-manager | GitHub | GitLab |
penguins-over-the-air | GitHub | GitLab |
penguins-pivot | GitHub | GitLab |
penguins-powerwash | GitHub | GitLab |
penguins-recovery | GitHub | GitLab |
immutable-filesystem_deving
linux-kernel_filesystem_deving
| Repo | GitHub | GitLab |
|---|---|---|
btrfs-dwarfs-framework | GitHub | GitLab |
kernel-patches | GitHub | GitLab |
linux-cachyos | GitHub | GitLab |
linux-distro-prefix | GitHub | GitLab |
linux-distro-stage3 | GitHub | GitLab |
linux-over-the-air | GitHub | GitLab |
linux-pivot | GitHub | GitLab |
linux-powerwash | GitHub | GitLab |
liquorix-unified-kernel | GitHub | GitLab |
liqxanmod | GitHub | GitLab |
lkf | GitHub | GitLab |
lkm | GitHub | GitLab |
ukm | GitHub | GitLab |
xanmod-unified-kernel | GitHub | GitLab |
incus_deving
| Repo | GitHub | GitLab |
|---|---|---|
Image-Server | GitHub | GitLab |
Incus-MacOS-Toolkit | GitHub | GitLab |
K8s-in-incus | GitHub | GitLab |
LXD-Manager | GitHub | GitLab |
blincus | GitHub | GitLab |
cluster-api-provider-incus | GitHub | GitLab |
ctde | GitHub | GitLab |
distrobuilder | GitHub | GitLab |
dotdrop | GitHub | GitLab |
ezpodman-sandbox | GitHub | GitLab |
gh-actions-manager | GitHub | GitLab |
incus | GitHub | GitLab |
incus-agent-sysvinit | GitHub | GitLab |
incus-app-container | GitHub | GitLab |
incus-cloud-init | GitHub | GitLab |
incus-compose | GitHub | GitLab |
incus-demo-server | GitHub | GitLab |
incus-deploy | GitHub | GitLab |
incus-docker | GitHub | GitLab |
incus-gha | GitHub | GitLab |
incus-goad | GitHub | GitLab |
incus-image-server | GitHub | GitLab |
incus-images | GitHub | GitLab |
incus-init-scripts | GitHub | GitLab |
incus-launch-vm-action | GitHub | GitLab |
incus-linux-toolkit | GitHub | GitLab |
incus-os | GitHub | GitLab |
incus-package-repo | GitHub | GitLab |
incus-sdk | GitHub | GitLab |
incus-ui-canonical | GitHub | GitLab |
incus-waydroid-toolkit | GitHub | GitLab |
incus-windows | GitHub | GitLab |
incus-windows-toolkit | GitHub | GitLab |
incus_container_gui_setup | GitHub | GitLab |
incus_container_manager | GitHub | GitLab |
incuslab | GitHub | GitLab |
infra-dashboard | GitHub | GitLab |
kapsule | GitHub | GitLab |
kapsule-incus-manager | GitHub | GitLab |
lxd_incus_gui_apps | GitHub | GitLab |
packer-plugin-incus | GitHub | GitLab |
packer-plugin-incuschroot | GitHub | GitLab |
podclaw | GitHub | GitLab |
polar | GitHub | GitLab |
setup-incus | GitHub | GitLab |
talos | GitHub | GitLab |
talos-incus | GitHub | GitLab |
terraform-incus-oci-image-updating | GitHub | GitLab |
waydroid-toolkit | GitHub | GitLab |
taubyte_deving
neon-deving
| Repo | GitHub | GitLab |
|---|---|---|
KPort | GitHub | GitLab |
docker-images | GitHub | GitLab |
kde-builder | GitHub | GitLab |
pkg-kde-dev-scripts | GitHub | GitLab |
pkg-kde-jenkins | GitHub | GitLab |
pkg-kde-tools | GitHub | GitLab |
qt-kde-team.pages.debian.net | GitHub | GitLab |
ubuntu-core | GitHub | GitLab |
ops
| Repo | GitHub | GitLab |
|---|---|---|
build-server | GitHub | GitLab |
flatpak-repo | GitHub | GitLab |
fork-sync-all | GitHub | GitLab |
git-sizer | GitHub | GitLab |
github-exporter | GitHub | GitLab |
headroom | GitHub | GitLab |
lowlighter-metrics | GitHub | GitLab |
org-mirror | GitHub | GitLab |
victoriametrics-metrics | GitHub | GitLab |
yaml-tooling_deving
| Repo | GitHub | GitLab |
|---|---|---|
CI-Debugger | GitHub | GitLab |
actions-orchestrator | GitHub | GitLab |
activitysmith-github-action | GitHub | GitLab |
agentshield | GitHub | GitLab |
blueprint | GitHub | GitLab |
confmgr | GitHub | GitLab |
deardir | GitHub | GitLab |
denoflow | GitHub | GitLab |
directory-model | GitHub | GitLab |
dirstructx | GitHub | GitLab |
fluent-github-actions | GitHub | GitLab |
gavi | GitHub | GitLab |
git-queue | GitHub | GitLab |
github-actions-virtualization-support | GitHub | GitLab |
github-actions-workflow-ts | GitHub | GitLab |
graphize | GitHub | GitLab |
katachi | GitHub | GitLab |
llm-docs-generator | GitHub | GitLab |
matrix-lock | GitHub | GitLab |
mnemonic | GitHub | GitLab |
models-generator | GitHub | GitLab |
pkgbuild-action | GitHub | GitLab |
project-memory-mcp | GitHub | GitLab |
pyvert | GitHub | GitLab |
scriptfs | GitHub | GitLab |
super-yaml | GitHub | GitLab |
system-cleanup-manager | GitHub | GitLab |
velite | GitHub | GitLab |
xml-yaml-json-converter | GitHub | GitLab |
y2j | GitHub | GitLab |
yaml-fuse | GitHub | GitLab |
yaml-sort | GitHub | GitLab |
yamler | GitHub | GitLab |
yamllint-ts | GitHub | GitLab |
cachyos_deving
| Repo | GitHub | GitLab |
|---|---|---|
CachyOS-PKGBUILDS | GitHub | GitLab |
CachyOS-Settings | GitHub | GitLab |
New-Cli-Installer | GitHub | GitLab |
ananicy-rules | GitHub | GitLab |
cachy-chroot | GitHub | GitLab |
cachy-update | GitHub | GitLab |
cachyos-repo-add-script | GitHub | GitLab |
chwd | GitHub | GitLab |
copr-linux-cachyos | GitHub | GitLab |
kernel-manager | GitHub | GitLab |
repo-manage-util | GitHub | GitLab |
scx-manager | GitHub | GitLab |
ai-agents_deving
| Repo | GitHub | GitLab |
|---|---|---|
OmoiOS | GitHub | GitLab |
PhoneixAI | GitHub | GitLab |
accessibility-agents | GitHub | GitLab |
clahub | GitHub | GitLab |
clawmetry | GitHub | GitLab |
netclaw | GitHub | GitLab |
nexa-gauge | GitHub | GitLab |
niko-claude-skills | GitHub | GitLab |
notificare | GitHub | GitLab |
pylibsmeta | GitHub | GitLab |
rust-systems_deving
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:
- Go to pre-flush-prep.yml
- Click Run workflow
- Leave all inputs at defaults for a standard flush
- Optional inputs:
skip_merge_prs=true— skip Step 2 if you've already merged PRs manuallyskip_cleanup=true— skip branch/run cleanup if quota is tightquota_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
| Step | Action | Skippable |
|---|---|---|
| 1 | Cancel stale/queued runs older than STALE_MIN | No |
| 2 | Merge green PRs on main | skip_merge_prs=true |
| 3 | Validate all configs (same as step 2 above) | No |
| 4 | Clean up stale branches and redundant base repos | skip_cleanup=true |
| 5 | Dispatch full-chain-flush | No |
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:
- Wait for the reset (up to 1 hour). The reset time is shown above.
- While waiting, use the time productively — all local operations (config validation, ShellCheck, pytest, file edits) work without quota.
- After reset, trigger
pre-flush-prep.ymlto 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:
- Trigger
queue-manager.ymlmanually — it deduplicates and evicts runs queued > 25 minutes. - If the queue is severely backed up, trigger
pre-flush-prep.ymlwithskip_merge_prs=trueandskip_cleanup=true— Step 1 aggressively clears stale runs before dispatching the flush. - 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:
- Generate a new PAT at https://github.com/settings/tokens
- Go to
rotate-token.yml→ Run workflow - Select the secret name from the dropdown
- Paste the new token value
- Leave
validatechecked - Update the expiry date in
AGENTS.mdtoken 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 leg | Fix |
|---|---|
| I-D-1896 → OSP | Trigger mirror-to-osp.yml manually |
| OSP → OOC | Trigger mirror-osp-to-ooc.yaml manually |
| OSP → GitLab | Trigger mirror-osp-to-gitlab.yml manually |
| GitLab → I-D-1896 | Trigger 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_urlortarget_nameinregistered-imports.json - Workflow added to
.github/workflows/but not registered inworkflow-priority-tiers.ymlorworkflow-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: ignoreif 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:
- Check the fork's
ota-self-update.ymlrun logs for the specific error - Common causes: fork has diverged significantly,
pinned_shais stale, or the fork's.ota/config.ymlhas an invalid field - To reset: update
pinned_shain the fork's.ota/config.ymlto the current upstream HEAD SHA, then re-triggerota-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-all — ota-release.yml triggers automatically.
Incident response checklist
For any production incident affecting the mirror chain:
- Check quota — if exhausted, wait for reset before doing anything else
- Check queue — trigger
queue-manager.ymlto clear pile-ups - Identify the broken leg — use
check-gitlab-sync.ymland manual inspection - Fix the specific leg — trigger the relevant mirror workflow directly
- Validate config — run all validators locally before triggering a flush
- Run pre-flush-prep — let it clean up and restart the chain
- 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:
| Mechanism | Where | What it does |
|---|---|---|
quota-reserve.sh | Runs every 30 min | Cancels 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 loop | Stops processing mid-run when time budget is exhausted. Prevents a single run from consuming all quota in one shot. |
workflow_min_quota() | Pre-flight steps | Returns 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)
| Workflow | min_quota | Low | Mid | High | Notes |
|---|---|---|---|---|---|
| Rotate Secret Token | 50 | 5 | 10 | 20 | Token validation + secret update |
| Queue Manager | 50 | 5 | 15 | 30 | Queued run list + cancel calls |
| Quota Reserve | 10 | 1 | 5 | 15 | rate_limit check (exempt) + cancels |
| Rate-Limit Re-trigger | 50 | 5 | 20 | 50 | Failed run scan + dispatch calls |
| Token Health Monitor | 50 | 5 | 10 | 20 | Token validation only |
| CI | 50 | 2 | 5 | 10 | ShellCheck + lint, minimal API |
| Pre-Flush Prep | 100 | 10 | 30 | 60 | PR list + check-run queries |
Tier 2 — High
| Workflow | min_quota | Low | Mid | High | Notes |
|---|---|---|---|---|---|
| Mirror Interested-Deving-1896 → OSP | 500 | 20 | 80 | 200 | 2 GraphQL + 1 REST/repo (check-runs, gated) |
| Mirror OSP → GitLab | 300 | 5 | 20 | 50 | 1 GraphQL for repo list; GitLab calls exempt |
| Sync Registered Imports | 200 | 5 | 15 | 30 | 1 GraphQL prefetch; REST only for new repos |
| Sync All Forks | 500 | 50 | 200 | 500 | 1 GraphQL + 1 REST merge-upstream per fork |
| Full Chain Flush | 1000 | 100 | 400 | 1000 | Orchestrates chain — cost is additive |
| Add Mirror Repo | 200 | 10 | 30 | 60 | Repo creation + webhook + dispatch |
Tier 3 — Medium
| Workflow | min_quota | Low | Mid | High | Notes |
|---|---|---|---|---|---|
| Update READMEs | 300 | 50 | 150 | 300 | Tree fetch + file reads/writes per repo |
| Create Missing READMEs | 200 | 20 | 80 | 200 | Same as Update READMEs, subset of repos |
| Inject Built-with-Ona Badges | 200 | 5 | 30 | 80 | 1 GraphQL (repo list + README); REST only on write |
| Reconcile Org References | 300 | 10 | 60 | 150 | 1 GraphQL repo list; pushedAt from cache |
| Check OSP-Bound CI Status | 300 | 50 | 150 | 300 | 4 REST/repo (check-runs not in GraphQL) |
| Rebase PRs | 100 | 5 | 20 | 50 | PR list + rebase trigger |
| Sync btrfs-devel Branches | 100 | 5 | 20 | 50 | Branch sync per tracked branch |
| Sync pieroproietti Forks | 100 | 10 | 40 | 100 | merge-upstream per fork branch |
| Setup OSP Mirror Workflows | 200 | 20 | 80 | 200 | 1 GraphQL + workflow/secrets per repo (not in GraphQL) |
| Upstream PRs from OSP + OOC | 200 | 20 | 80 | 200 | PR creation/update per diverged repo |
| Upstream Direct Commits from OSP + OOC | 200 | 20 | 80 | 200 | Commit compare + PR creation |
| Sync to GitLab | 100 | 5 | 20 | 50 | GitHub reads; GitLab writes exempt |
| Sync to GitLab Variant | 100 | 5 | 20 | 50 | Same as Sync to GitLab |
| Sync from GitLab | 100 | 5 | 20 | 50 | GitLab reads + GitHub writes |
| Notification Poller | 50 | 1 | 5 | 15 | Single notifications call + optional dispatch |
Tier 4 — Low (cancelled first)
| Workflow | min_quota | Low | Mid | High | Notes |
|---|---|---|---|---|---|
| Translate READMEs | 100 | 10 | 40 | 100 | File read + write per README |
| LTS README Standardisation | 100 | 10 | 40 | 100 | File read + write per LTS repo |
| Generate OSP Dependency Graph | 100 | 20 | 60 | 150 | README + package.json reads per repo |
| Upstream Workflow Proposal | 50 | 5 | 20 | 50 | Workflow file reads + PR creation |
| Update Infrastructure Dependencies | 50 | 5 | 15 | 30 | Dependabot config + PR creation |
| Mirror Artifacts | 200 | 10 | 50 | 150 | 2 GraphQL; release asset downloads exempt |
| Mirror Releases | 200 | 10 | 50 | 150 | 2 GraphQL + 1 REST releases list per repo |
| Cleanup Stale Branches | 200 | 10 | 60 | 200 | 1 GraphQL + 1 REST compare per branch |
| OTA Discover | 100 | 10 | 40 | 100 | Fork list + config reads per fork |
| OTA Self-Update | 50 | 5 | 15 | 30 | Config read + PR creation |
| Mirror Orgs | 100 | 20 | 60 | 150 | Repo list + description reads per org |
| Resolve CI Failures | 100 | 10 | 40 | 100 | Failed 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.
| Category | Before | After |
|---|---|---|
quota-reserve runs/day | 144 | 48 |
queue-manager runs/day | 96 | 48 |
notify-poller runs/day | 12 | 6 |
rate-limit-rerun runs/day | 12 | 6 |
mirror-artifacts runs/day | 6 | 3 |
mirror-osp-to-gitlab runs/day | 6 | 3 |
sync-pieroproietti-forks runs/day | 6 | 3 |
sync-to-gitlab-variant runs/day | 6 | 3 |
REST → GraphQL conversion log
Scripts converted from per-repo REST loops to batched GraphQL calls:
| Script | Savings/run | Runs/day | Saved/day |
|---|---|---|---|
sync-registered-imports.sh | ~100 | 4 | ~400 |
mirror-osp-to-gitlab.sh | ~2 | 3 | ~6 |
reconcile-org-refs.sh | ~100 | 6 | ~600 |
inject-badges.sh | ~50 | 3 | ~150 |
cleanup-branches.sh | ~200 | 1 | ~200 |
mirror-releases.sh | ~50 | 4 | ~200 |
mirror-artifacts.sh | ~50 | 3 | ~150 |
ota-discover.sh | ~50 | 1 | ~50 |
mirror-orgs.sh | ~60 | 1 | ~60 |
setup-osp-mirrors.sh | ~100 | 4 | ~400 |
| Total | ~2,216/day |
Phase 2: observed cost tracking (planned)
Phase 2 will add lightweight instrumentation to measure actual REST consumption per run:
scripts/includes/quota-instrument.sh— recordsremaining_beforeandremaining_afteras workflow step summary annotationsupdate-quota-costs.yml— weekly workflow that reads the last 30 run summaries via GraphQL, computes p50/p95 per workflow, and commits updated values back toconfig/workflow-quota-costs.ymlwithbasis: 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:
| Operation | Cost |
|---|---|
gh api / REST API call | 1 req |
| Listing workflow runs | 1 req per page |
| Cancelling a run | 1 req |
| Triggering a workflow dispatch | 1 req |
| Checking job status | 1 req per job |
| GraphQL query | Separate quota (5,000 points/hr) — unaffected by REST exhaustion |
How fork-sync-all burns it:
- Every
workflow_runtrigger fires a new run, which itself may call the API rate-limit-rerun.yml(formerly hourly) scans all recent failed runsstuck-run-detector.yml(formerly hourly) lists all queued/in-progress runstranslate-readmes.ymlwas 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_dispatchtriggers 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):
mirror-orgs-watchdogfired after every mirror completion (5 workflows × hourly cadence = ~120 runs/day), each consuming ~1 min even on successupdate-readmestriggered after 7 workflows including high-frequency syncsinject-badgestriggered after mirror workflows that run hourlystuck-run-detectorandrate-limit-rerunran hourly as meta-workflows, each consuming minutes to manage other workflowsworkflow_runlisteners fired on everycompletedevent (success, failure, cancelled) — not just on the outcomes they actually needed
Detecting exhaustion:
Symptoms (in order of appearance):
ubuntu-latestjobs queue but never start- No in-progress runs despite many queued
- Runs queued for hours with 0 runners active
- Billing API returns 404 (needs
userOAuth 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_dispatchmanually 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:
- Runner minutes exhaust mid-job → job hangs in
in_progress - Next scheduled run queues behind it (
cancel-in-progress: false) - The in-progress run never finishes → queue grows indefinitely
- 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:
| Workflow | cancel-in-progress | Reason |
|---|---|---|
sync-template | false | Propagates files to 35 repos — partial sync leaves repos inconsistent |
mirror-releases | false | Partial mirror leaves releases incomplete |
lts-readmes | false | Mid-run cancel leaves some repos un-standardised |
mirror-osp-to-gitlab | false | Partial GitLab mirror |
create-readmes | false | Mid-run cancel leaves some repos without READMEs |
mirror-artifacts | false | Partial artifact mirror |
| All others | true | Newer 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):
| Workflow | Gate |
|---|---|
mirror-orgs-watchdog | conclusion == 'failure' |
create-readmes | conclusion == 'success' |
inject-badges | conclusion == 'success' |
lts-readmes | conclusion == 'success' |
mirror-osp-to-gitlab | conclusion == 'success' |
translate-readmes | conclusion == 'success' (on gate job) |
update-readmes | conclusion == 'success' |
dwarfs-pack-caller | conclusion == 'success' |
rebase-lts | conclusion == 'success' |
Current Workflow Schedule Summary
Workflows that run on a schedule and their cadence after May 2026 fixes:
| Workflow | Schedule | Notes |
|---|---|---|
mirror-to-osp | Hourly :00 | Core mirror |
mirror-releases | Hourly :00 | |
mirror-artifacts | Hourly :08 | |
mirror-osp-to-gitlab | Hourly :24 | |
upstream-prs | Hourly :32 | |
upstream-commits | Hourly :40 | |
reconcile-org-refs | Hourly :56 | |
sync-pieroproietti-forks | Hourly :05 | |
notify-poller | Every 30 min | |
stuck-run-detector | Every 6h :20 | Reduced from hourly |
rate-limit-rerun | Every 6h :12 | Reduced from hourly |
rate-limit-budget-report | Daily 11:00 | Reduced from every 2h |
update-readmes | Daily 10:20 | |
create-readmes | Daily 10:30 | |
inject-badges | Daily 10:40 | |
translate-readmes | Daily 10:50 | |
sync-forks | Daily 11:30 | |
mirror-orgs-full | Daily 10:00 | |
lts-readmes | Monthly 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.
Self-Hosted Runner Setup (Recommended)
To eliminate the monthly minute cap entirely, add a self-hosted runner:
- Go to Settings → Actions → Runners → New self-hosted runner
- Follow the setup instructions for your host OS
- Change workflow
runs-onfromubuntu-latesttoself-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
| Limit | Resets |
|---|---|
| GitHub API rate limit (REST) | Top of every hour |
| GitHub API rate limit (GraphQL) | Top of every hour (separate quota) |
| GitHub Actions minutes | Billing 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
-
Create the workflow file in
.github/workflows/ -
Register in priority tiers — add an entry to
config/workflow-priority-tiers.ymlusing the workflow'sname:field (not the filename):- name: "My New Workflow" tier: 3 # MEDIUM — adjust based on criticalityTier guide: 1=CRITICAL (never cancelled), 2=HIGH (mirror chain), 3=MEDIUM (default), 4=LOW (cancelled first under quota pressure)
-
Register in workflow-sync — add to
config/workflow-sync.yml:- Under
github_onlyif it has no GitLab CI counterpart (most workflows) - Under
pairedif it has a matching GitLab CI job
- Under
-
Add a concurrency group if triggered by
scheduleorworkflow_run:concurrency: group: my-workflow-name cancel-in-progress: true -
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" -
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:
- Place under
vendor/<component-name>/ - Strip all distro-specific or org-specific hardcoded defaults — see the agnostic rule below
- Add a
README.mdwith a "Before the first deploy" section covering all required CI variables - 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.