Skip to content

feat(ppl): optimize init-job checkout (blobless + sparse) behind a feature flag#1063

Merged
skipi merged 8 commits into
mainfrom
db/init-job-blobless-sparse-checkout
Jun 25, 2026
Merged

feat(ppl): optimize init-job checkout (blobless + sparse) behind a feature flag#1063
skipi merged 8 commits into
mainfrom
db/init-job-blobless-sparse-checkout

Conversation

@DamjanBecirovic

@DamjanBecirovic DamjanBecirovic commented Jun 15, 2026

Copy link
Copy Markdown
Collaborator

What

The pipeline initialization (compilation) job only needs the pipeline YAML and
the Git history (commits/trees, used by change_in) to compile a pipeline — not
the full repository working tree. For large repositories the full checkout
dominates the init-job runtime (cloning hundreds of MiB and materializing tens
of thousands of files on every run).

This makes the init job perform a blobless partial clone + sparse checkout of
the pipeline directory, gated by a per-organization feature flag and disabled
when pre-flight checks are present.

How it works

The init job emits SEMAPHORE_GIT_PARTIAL_CLONE_FILTER=blob:none and
SEMAPHORE_GIT_SPARSE_CHECKOUT_PATHS=<working_dir> before checkout. The
optimization is applied only when both hold:

  • there are no pre-flight checks (their custom commands may rely on the full
    working tree), and
  • the sparse_checkout_init_job feature is enabled for the organization.

The feature check fails closed — a missing org id or an unreachable Feature
service keeps the standard full checkout. change_in keeps working because it
relies only on git diff --name-only / --shortstat / merge-base, which need
tree/commit objects (present in a blobless clone), not blob contents.

Changes

  • proto: generate InternalApi.Feature stubs; add INTERNAL_API_LOCAL_PATH
    support to the proto Makefile so they can be regenerated from a local
    internal_api checkout.
  • ppl: add the feature_provider dependency and a FeatureHub-backed provider
    • gRPC client (INTERNAL_API_URL_FEATURE); Ppl.Features exposes the
      fail-closed flag check; init FeatureProvider and a :feature_cache in the
      application supervisor.
  • ppl: gate the optimized checkout in the compilation init-job command
    generation; auto-disable when pre-flight checks are configured.
  • Build: move ppl's Docker build context to the repository root (matching the
    top-level services) so feature_provider, which lives outside plumber/, is
    included in the image.
  • Tests: feature-flag gate + provider coverage, with a Feature gRPC mock; plus a
    small fix to make an unrelated integration test's env restore nil-safe.

Note: yaml_elixir is pinned to ~> 1.3 via override because the umbrella's
YAML validators rely on 1.x return semantics; this means feature_provider's
YAML provider is not used here (the FeatureHub gRPC provider is). A follow-up
is tracked to align on yaml_elixir 2.x for YAML-defined features.

Dependencies

This relies on the toolbox checkout partial/sparse support and the spc
commands_file on-demand fetch being released (and the toolbox image rebuilt).

Testing

  • Ppl: QA and Ppl: Integration QA green; dev and prod image builds validated.

Related PRs

🤖 Generated with Claude Code

DamjanBecirovic and others added 4 commits June 9, 2026 18:06
… checks

The pipeline initialization (compilation) job only needs the pipeline YAML
and the Git history (trees/commits, used by `change_in`) to compile the
pipeline - not the full repository working tree. For large repositories the
full checkout dominates the init job runtime (cloning hundreds of MiB and
materializing tens of thousands of files).

When there are no pre-flight checks, instruct `checkout` (via the
SEMAPHORE_GIT_PARTIAL_CLONE_FILTER and SEMAPHORE_GIT_SPARSE_CHECKOUT_PATHS
env vars) to perform a blobless partial clone with a sparse working tree
limited to the pipeline directory. When pre-flight checks are configured,
their custom commands run after the predefined ones and may rely on the full
working tree, so the standard full checkout is kept.

Note: this relies on the corresponding toolbox `checkout` support and the spc
`commands_file` on-demand fetch. Per-organization feature-flag gating is added
in a follow-up commit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wire the FeatureProvider/FeatureHub stack into plumber and gate the blobless +
sparse init-job checkout behind the per-organization `sparse_checkout_init_job`
feature flag. The optimization now applies only when both hold: there are no
pre-flight checks AND the feature is enabled for the organization. The check
fails closed (missing org id or unreachable Feature service => standard full
checkout).

- proto: generate InternalApi.Feature stubs; add INTERNAL_API_LOCAL_PATH support
  to the proto Makefile so they can be regenerated from a local internal_api
  checkout, plus a feature.proto generation step.
- ppl: add feature_provider dep (pin yaml_elixir ~> 1.3 via override since
  definition_validator requires 1.x and we only use the FeatureHub provider).
- Ppl.FeatureClient: gRPC client for the Feature service (INTERNAL_API_URL_FEATURE).
- Ppl.FeatureHubProvider: FeatureProvider.Provider backed by the Feature service.
- Ppl.Features: thin, fail-closed wrapper exposing sparse_checkout_init_job_enabled?/1.
- Ppl.Application: init FeatureProvider and start the :feature_cache Cachex.
- config: FeatureHub provider with CachexCache (no cache in test env).
- tests: Feature gRPC mock + coverage for the gate decision and provider path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…e_provider

feature_provider lives at the repository root, outside the plumber/ tree, so it
was not reachable from ppl's previous Docker build context (plumber/), breaking
`mix deps.get` in the image build. Move ppl's build context to the repository
root (matching front/zebra/secrethub) and prefix the Dockerfile COPY paths with
plumber/, plus COPY feature_provider into the image. Update the Makefile build
path and docker-compose context/dockerfile accordingly.

Validated locally by building both the dev (mix compile) and prod (mix release)
image targets.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The integration test restored INTERNAL_API_URL_ARTIFACTHUB / _PROJECT in on_exit
via System.put_env/2, which raises when the previous value was nil (the vars are
not set globally; they depend on test ordering/sharding). Guard the restore so a
nil previous value deletes the env var instead of crashing. Surfaced after a new
test file shifted integration sharding.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The feature flag check runs in the pipeline initialization hot path (the compile
task is built and awaited inside a deadline-bounded looper step). Harden it so
Feature service availability cannot couple into init latency:

- Ppl.FeatureClient: add a 1s per-call gRPC deadline and tighten the Wormhole
  backstop to 1.5s, so the call fails closed fast instead of blocking the init
  path for seconds when feature-hub is slow/unreachable.
- Ppl.Features: memoize the (fail-closed) boolean result for 30s in the
  :feature_cache. FeatureProvider only caches successful responses, so without
  this a feature-hub blip would make every init pay the timeout.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
skipi added a commit to semaphoreci/toolbox that referenced this pull request Jun 23, 2026
…#542)

## What

Adds an opt-in "optimized" checkout path to the `checkout` toolbox
command, for
callers that only need the Git history (commits/trees) and a subset of
the
working tree rather than the full repository. It is controlled by two
env vars
and is fully backwards compatible — when neither is set, `checkout`
behaves
exactly as before.

```
SEMAPHORE_GIT_PARTIAL_CLONE_FILTER   e.g. "blob:none"  -> git clone --filter=...
SEMAPHORE_GIT_SPARSE_CHECKOUT_PATHS  e.g. ".semaphore" -> cone-mode sparse checkout
```

When either is set, `checkout` performs a `--no-checkout` (optionally
filtered)
clone and a cone-mode sparse checkout limited to the requested paths,
handling
push/branch, pull-request, and tag refs. For large repositories this
avoids
downloading blob content and materializing tens of thousands of files
when only
a small subset is needed.

## Changes

- `libcheckout`: new `checkout::optimized` /
`checkout::configure_sparse` paths,
dispatched from `checkout()` only when the new env vars are present.
Existing
  `shallow` / `refbased` / `use-cache` paths are untouched.
- **Graceful fallback:** `git sparse-checkout` (cone mode) requires git
>= 2.25.
On older clients the optimized path detects the missing subcommand and
falls
  back to a standard checkout, so the request degrades into a correct
(non-optimized) clone instead of a full checkout from a blobless clone.
- `tests/libcheckout.bats`: capability-aware tests for push/branch, PR
and tag —
asserting the sparse working tree where supported and the full-tree
fallback
  where not.
- CI: the macOS `xcode26 arm` block's prologue ran `brew upgrade
ruby-build`,
which newer Homebrew turns into an interactive `[y/n]` prompt that
blocked on
stdin and stopped the block (also broken on `master`). Pipe `yes` so it
  proceeds non-interactively.

## Testing

- bats `libcheckout` suite green on Docker, Linux, Ubuntu 24.04, and
Alpine 3.9
(git 2.20 -> exercises the fallback), plus `shellcheck -s bash
libcheckout`.

> Pairs with a consumer change (a pipeline initialization job that sets
these env
> vars) and a related compiler change; the env-var interface is additive
and safe
> to merge independently.

## Related PRs

- semaphoreio/semaphore#1063
- semaphoreci/spc#59

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Mikołaj Kutryj <mikolaj.kutryj@gmail.com>
@skipi skipi marked this pull request as ready for review June 25, 2026 07:03
@skipi skipi requested review from dexyk, loadez and skipi as code owners June 25, 2026 07:03
Comment thread plumber/ppl/config/config.exs
@skipi skipi merged commit c940069 into main Jun 25, 2026
2 checks passed
@skipi skipi deleted the db/init-job-blobless-sparse-checkout branch June 25, 2026 12:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

3 participants