Cross-Project Pipelines

glci supports both child pipelines (trigger: include:) and cross-project pipelines (trigger: project:) locally. Child pipelines share the parent’s Docker infrastructure. Cross-project triggers resolve the target project from a local directory or by cloning it from GitLab.

Child pipelines (trigger: include:)#

Child pipelines work the same as production GitLab CI parent-child pipelines. All include formats (string shorthand, local: map, artifact-sourced includes (artifact:/job:, see below), forward:, and per-include inputs:) are supported. A trigger: include: may list up to 3 files, which are merged into the child pipeline (matching GitLab).

glci-specific behavior#

Variable forwarding#

forward: controls what the parent passes to the downstream pipeline. The defaults match GitLab’s FORWARD_DEFAULTS:

Forward settingDefaultWhat it passes
yaml_variablestrueThe trigger job’s YAML variables: the parent’s global variables: merged with the trigger job’s own variables: (job overrides global)
pipeline_variablesfalseVariables passed via --env (CLI/manual) and --env-file/dotenv (dependency) variables

Because yaml_variables defaults to true, a trigger job with no forward: block still forwards the parent’s global variables and the trigger job’s own variables:. Set yaml_variables: false to suppress both.

Forwarded yaml_variables values are expanded in the context of the trigger job (the upstream pipeline) before being forwarded, so a value like SERVICE_PROJECT_URL: $CI_PROJECT_URL resolves to the parent project’s URL — matching GitLab. Use $$ to pass a literal dollar sign. If an expanded value references a masked variable, the forwarded copy stays masked.

Trigger inputs: are not forwarded as variables — like GitLab, they are used only for spec:inputs interpolation of the included config.

Dynamic child pipelines (trigger: include: artifact:)#

A child pipeline can be generated at runtime by a job and passed to the trigger as an artifact, matching GitLab’s dynamic child pipelines. A generator job writes the child pipeline YAML to a file and exposes it as an artifact; the trigger job references it with artifact: and job::

generate-child-pipeline:
  stage: generate
  script:
    - ./scripts/generate.sh > generated-pipeline.yml
  artifacts:
    paths:
      - generated-pipeline.yml

trigger-child-pipeline:
  stage: trigger
  needs:
    - job: generate-child-pipeline
      artifacts: true
  trigger:
    include:
      - artifact: generated-pipeline.yml
        job: generate-child-pipeline
    strategy: depend

glci resolves the artifact from the generator job’s output and runs it as the child pipeline, with full support for strategy: depend, variable forwarding, and per-include inputs: — the same as a static local: include. Notes:

Cross-project pipelines (trigger: project:)#

Cross-project triggers (trigger: project:) work the same as production GitLab CI, but glci needs to know where the target project’s source code lives locally.

Mapping projects to local directories#

Use --project-dir to map a project path to a local checkout:

glci run --project-dir group/other-project=../other-project

Or configure the mapping permanently in .glciconfig.toml:

[projects."group/other-project"]
dir = "../other-project"
branch = "main"  # optional: override which branch to use

Relative paths in dir are resolved against the parent project’s working directory. The target directory must be a git repository.

Resolution order#

When glci encounters a trigger: project: job, it resolves the target in this order:

  1. CLI --project-dir mapping – highest priority
  2. Config [projects] section in .glciconfig.toml or ~/.glci/config.toml (project-level overrides global)
  3. Git clone from GitLab – if a token is available, glci clones the target project as a bare repo (works for public repos without a token)
  4. Error with instructions – if none of the above succeed, glci prints a message showing how to configure the mapping

What requires a GitLab token#

FeatureToken requiredWorks offline
trigger: include: (child pipeline)NoYes
trigger: project: with --project-dirNoYes
trigger: project: with [projects] configNoYes
trigger: project: (auto-clone from GitLab)Yes (or public repo)No
include: project: in YAMLYesNo
include: component: in YAMLYesNo
include: local: in YAMLNoYes
include: remote: (HTTP URL)NoNeeds network
include: template:NoNeeds network

Cross-project variables#

Cross-project pipelines get fresh CI variables derived from the target project’s git state rather than inheriting the parent’s. This means CI_PROJECT_NAME, CI_COMMIT_SHA, CI_COMMIT_REF_NAME, and other git-derived variables reflect the target project.

The target pipeline receives CI_PIPELINE_SOURCE=pipeline (distinct from child pipelines which use parent_pipeline).

The cross-project pipeline also gets CI_REGISTRY, CI_REGISTRY_USER, and CI_REGISTRY_IMAGE pointing at the shared embedded registry, so jobs in the triggered project can build and push images just like the parent. CI_REGISTRY_IMAGE reflects the target project’s path. CI_REGISTRY_PASSWORD is injected per job (the job token), the same way it works in the parent pipeline.

Variable forwarding uses the same forward: rules and defaults as child pipelines (see Variable forwarding above): yaml_variables (default true) forwards the parent’s global variables: merged with the trigger job’s own variables:; pipeline_variables (default false) forwards CLI/dotenv variables. Forwarded values are expanded in the trigger job’s context ($CI_PROJECT_URL resolves to the parent project’s URL, not the target’s), and trigger inputs: are used only for spec interpolation, not forwarded as variables.

Dirty mode for cross-project targets#

When dirty mode is enabled (the default), glci overlays uncommitted and untracked files from local project directories onto the bare repo sent to the runner. This lets you test changes across multiple projects without committing first.

Multi-project setups#

For monorepos, standard child pipelines with different include paths work as expected. You can run a specific trigger job with glci run trigger-frontend.

For separate repositories that form a pipeline chain, map them all in .glciconfig.toml:

[projects."myorg/shared-lib"]
dir = "../shared-lib"

[projects."myorg/deploy-tools"]
dir = "../deploy-tools"
branch = "main"

include: project: in YAML#

include: project: works the same as production GitLab CI – it fetches YAML files from other projects via the GitLab Repository Files API at parse time. This always requires a GitLab token (see the token table above).

Nested includes within the fetched file that use local: are automatically rewritten to project: includes targeting the same remote project, matching GitLab’s behavior.

include: component: in YAML#

include: component: fetches CI/CD components from GitLab via the API at parse time. This always requires a GitLab token.

Version selectors#

Components are referenced with a version suffix after @. In addition to exact tag references, glci supports version selectors that resolve to the latest matching semver release tag:

SelectorExampleResolves to
@~latestmygroup/mycomp@~latestLatest stable semver release tag
@~Nmygroup/mycomp@~2Latest tag with major version 2 (e.g. 2.5.1)
@~N.Mmygroup/mycomp@~2.3Latest tag with major.minor 2.3 (e.g. 2.3.7)

Pre-release tags (e.g. 1.0.0-rc1) are excluded from version selector resolution. Only stable semver tags are considered.

Version selectors query the GitLab Tags API, so they always require a GitLab token and network access. Exact tag references (e.g. @1.2.3) also require a token but skip the tag listing step.

Troubleshooting#

If a trigger job fails, check the daemon logs for resolution and parsing errors:

glci log <pipeline-id> trigger-job-name
tail -f ~/.glci/daemon.log
Esc