Guides changes to the regular GitHub Actions CI in this repository: validation and reusable workflows, custom actions, CI scripts, Dockerfiles, and the Smart CI configuration that drives them.
Read the CI developer docs under docs/dev/ci/github_actions first — they are the authoritative, repo-specific reference and this skill is a checklist on top of them. Keep them in sync when behavior changes (see Skill self-improvement). The key pages:
| Topic | Doc |
|---|---|
| Big picture, workflow structure, triggers, results/artifacts/logs | overview.md |
Reusable job_*.yml workflows |
reusable_workflows.md |
| Custom composite actions | custom_actions.md |
| Smart CI (skip unaffected jobs) | smart_ci.md |
Runners (runs-on) |
runners.md |
Docker images / handle_docker |
docker_images.md |
| Caches (GHA / shared drive / sccache) | caches.md |
| Adding tests (step / job / workflow) | adding_tests.md |
| OpenVINO Provider (prebuilt artifacts) | openvino_provider.md |
| Workflow security | security.md |
For framework syntax, use the official GitHub Actions documentation. If user requires an out-of-scope feature, consult the official documentation.
Out of scope: gh-aw agentic workflows (.github/workflows/*.md + *.lock.yml, e.g. ci-doctor)
— use the ov-agentic-workflows skill instead.
Repository layout
- Workflows —
.github/workflows/- Validation workflows: named after the OS/config, e.g.
ubuntu_22.yml,windows_vs2022_release.yml,mac_arm64.yml,linux_arm64.yml,android.yml. Entry points withon:triggers; they wire togetherBuild+ test jobs. - Reusable workflows:
job_*.yml(e.g.job_python_unit_tests.yml,job_cxx_unit_tests.yml). Called viauses: ./.github/workflows/job_*.ymlwithon: workflow_call:inputs. Not triggered directly.
- Validation workflows: named after the OS/config, e.g.
- Custom actions —
.github/actions/(compositeaction.yml):setup_python,system_info,smart-ci,handle_docker,openvino_provider,store_artifacts/restore_artifacts,cache, etc. - CI scripts —
.github/scripts/(Python helpers:workflow_rerun/,external_pr_labeller.py,check_copyright.py, ...). - Dockerfiles —
.github/dockerfiles/ov_build/**andov_test/**, plus thedocker_tagfile. - Smart CI config —
.github/labeler.yml(path globs → component/label) and.github/components.yml(component dependency graph). - Scans/meta —
workflows_scans.yml(CodeQLactions+ semgrep on workflow changes),dependency_review.yml.
Golden rules
- Pin third-party actions to a full commit SHA, with the version in a trailing comment. First-party
./.github/actions/*are referenced by path, not pinned.uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - Least-privilege
permissions:. Start frompermissions: read-all(workflow level) and grant the minimum extra scope at the job level only where needed. Never widen without a reason. - Follow security.md — it is the source of truth.
Key rules: never use
pull_request_target, never hard-code secrets, and treat allgithub.*/github.event.*user-controlled values as untrusted (route them throughenv:or action inputs, never interpolate directly intorun:shell). Ping the CI task force for anything involving secrets or elevated triggers. - Put shared job logic in a reusable
job_*.yml, not copy-paste. If the same job appears in more than one validation workflow, it should be aworkflow_callreusable workflow parameterized byrunner,image/container, andaffected-components. - Respect Smart CI. Test jobs/steps that validate a specific component must gate on
fromJSON(needs.smart_ci.outputs.affected_components).<COMPONENT>.{build,test}and listSmart_CIinneeds. Do not make an expensive job run unconditionally. - Custom Docker images come from
handle_docker, not hard-coded tags. Reference build/test images as${{ fromJSON(needs.docker.outputs.images).ov_build.<name> }}and addDockertoneeds. Plain passthrough images must use the ACR mirroropenvinogithubactions.azurecr.io/..., neverdocker.io. - Keep changes scoped and validate before finishing (see Validation).
.github/**is owned by@openvinotoolkit/openvino-ci-maintainersand changes there are labelcategory: CI(workflows also getgithub_actions).
Key conventions
- Runner selection (
runs-on) — self-hosted Azure poolsaks-{os}-{cores}-cores-{ram}gb[-arm]for heavy build/test; GitHub-hosted (ubuntu-22.04, ...) for light jobs (labelers, style). GPU jobs use[ self-hosted, gpu|igpu|dgpu ]and must run in Docker. Azureaks-*runners are required to pull from the ACR / use custom images. Match cores to parallelism (see runners.md). - Containers — most self-hosted jobs run in a
container:. Mount the shared drive withvolumes: [ /mount:/mount ]and add${{ github.workspace }}:${{ github.workspace }}where the workspace must be identical inside/outside the container. - Caches — GHA cache (
actions/cache, ≤10 GB) for small deps; shared drive (/mount/..., e.g.PIP_CACHE_PATH: /mount/caches/pip/linux) for large assets on Linux self-hosted;sccache→ Azure Blob for C/C++ build cache (needsSCCACHE_AZURE_*env +CMAKE_*_COMPILER_LAUNCHER: sccache+SCCACHE_AZURE_KEY_PREFIX). - Artifacts —
Buildjob packs and uploads; test jobsneeds: Buildand download. Follow the existingstore_artifacts/restore_artifactsactions and artifact-name conventions in the workflow. timeout-minutes— always set a sensible per-job timeout.
Common tasks
Add a test (choose the smallest unit)
Follow adding_tests.md:
- New tests inside an existing suite → no workflow change needed.
- New step in an existing job → add a
name+runstep, gate withif: fromJSON(inputs.affected-components).<COMPONENT>.testwhen component-specific. - New job → add to the right validation workflow (or a
job_*.yml); setneeds: [Build, Smart_CI],runs-on,container,timeout-minutes, and a Smart CIif:.
Add / edit a reusable workflow (job_*.yml)
- Give it
on: workflow_call:with typedinputs(runner,image,affected-components,python-version, ...) andpermissions: read-all. - Reference it from validation workflows via
uses: ./.github/workflows/job_<name>.ymlwithwith:andneeds: [ Build, Smart_CI ]. - Keep the input contract minimal and documented via
description:.
Wire up Smart CI for a component
- Map source paths → label in
.github/labeler.yml('category: X': [globs]). - Declare dependents in
.github/components.ymlunderrevalidate:(build+test) /build:(build only); use[]for none, or'all'to force full validation. Dependencies are not transitive. - Add
Smart_CIto the validating job'sneedsand gate withif: fromJSON(needs.smart_ci.outputs.affected_components).<COMPONENT>.{build,test}. Keep the same condition on every step/job in the dependency chain — a skipped step feeding an ungated dependent leaves it running against missing outputs. - Aggregate results into the
Overall_Statusjob (itneeds:the real jobs and reports one required check). A workflow that must be required cannot use apaths:filter — a filtered-out run reports no status and blocks the merge queue; rely on Smart CI +Overall_Statusinstead.
Add / change a custom action
- Composite
action.ymlunder.github/actions/<name>/. Declareinputs(withdescription,required,default) andruns: using: composite. Everyrunstep needs an explicitshell:. - Reference untrusted input via
env:inside the step, not inline interpolation. - Update custom_actions.md if the action is user-facing.
Choosing the implementation environment
- Python is the default and preferred implementation language for a custom action's logic. Use it for
any action that does not need extensive access to the GitHub (Actions) API — file/artifact
handling, environment setup, packaging, running tools, parsing, etc.
- If the action needs a
requirements.txt, it must pin the full dependency tree, not just top-level packages. Generate it from a clean environment withpip freeze:
This makes installs reproducible and pinned. Regenerate the same way whenever deps change.python3 -m venv /tmp/act-env && . /tmp/act-env/bin/activate pip install <top-level-deps> # only the packages you directly import pip freeze > .github/actions/<name>/requirements.txt
- If the action needs a
- Use JavaScript/TypeScript only when the action uses the GitHub (Actions) API extensively — the
Octokit/
@actions/*toolkit gives first-class typed access to it. The bundled.github/actions/cacheaction is the reference example of a JS-based action. - When in doubt, prefer Python and keep API interaction minimal.
Add / use a custom Docker image
Follow docker_images.md: add a Dockerfile under
.github/dockerfiles/{ov_build,ov_test}/<platform>/, ensure a Docker job runs handle_docker with the
image path in images:, add Docker to consumers' needs, and set
image: ${{ fromJSON(needs.docker.outputs.images).<group>.<name> }}. When adding a new env-setup script,
add it under category: docker_env in labeler.yml, exclude it from .dockerignore, and bump
.github/dockerfiles/docker_tag (handle_docker prompts you).
Change a runner or container
Pick the pool from runners.md; keep container
volumes/options (shared drive, sccache) consistent with sibling jobs. GPU → Docker + self-hosted label.
Validation before finishing
- YAML/lint: run
actionlintif available (actionlint .github/workflows/<file>.yml); otherwise sanity-check YAML parses. Mirror whatworkflows_scans.yml(CodeQLactions+ semgrep) anddependency_review.ymlenforce — those run on any.github/workflows/**change. - Pinning: every third-party
uses:is a full SHA + version comment. - Permissions: workflow defaults to least privilege; extra scopes are job-scoped and justified.
- Smart CI: new component-specific jobs/steps are gated and
Smart_CIis inneeds. - Scope: only intended files changed; no stray
docker_tag/labeler/components edits unless required. - Do not hand-edit any
*.lock.yml— those belong to agentic workflows.
Pitfalls to check
- Unpinned or tag-pinned third-party action — supply-chain risk; CodeQL/semgrep will flag it.
- Untrusted input in
run:—${{ github.event.* }}interpolated into shell is an injection vector; route throughenv:. - Over-broad
permissions:— especiallywritescopes at workflow level. - Job that ignores Smart CI — burns limited self-hosted/GPU capacity on unaffected PRs.
- Smart CI condition mismatch across a dependency chain — a step skipped by a Smart CI
if:whose dependent step/job lacks the same condition runs against missing outputs/artifacts. Gate the whole chain consistently. paths:filter on a required workflow — filtered-out runs report no status and hang the merge queue; use Smart CI +Overall_Statusinstead ofpaths:.- Hard-coded
docker.io/ non-ACR image onaks-*— pulls fail or hit rate limits; use the ACR mirror orhandle_dockeroutput. - Missing
Docker/Smart_CI/Buildinneeds— races or missing artifacts/inputs. - Editing a
job_*.ymlinput contract without updating every caller'swith:block. - Docker env change without
docker_tagbump — the image won't rebuild; handle_docker fails the check. - Missing
timeout-minutes— a hung job can occupy a runner indefinitely. - GPU job without Docker — required on persistent GPU runners.
Skill self-improvement
Keep this skill and the CI docs in sync with reality. When a change reveals a new rule, pattern, or footgun:
- New reusable workflow / custom action / Dockerfile convention — note it under Common tasks and,
if user-facing, in the matching page under
docs/dev/ci/github_actions/. - New footgun (a pinning/permission/Smart CI/Docker/cache mistake that bit you) — add it to Pitfalls.
- A rule becomes obsolete (a workflow removed, a runner pool renamed, a path moved) — update or remove the stale entry instead of leaving it.
- Keep it concise — prefer linking to the (updated) doc over duplicating detail; this file is a short actionable checklist, not a second copy of the documentation.
- This directory is reachable via both
.agents/skills/and.claude/skills/(the former is a symlink to the latter), so a single edit updates both — do not create a duplicate copy.