Skip to content

fix: install system dependencies even on a cache hit - #1

Merged
Ch4s3 merged 2 commits into
mainfrom
fix/install-runtime-deps-on-cache-hit
Jul 23, 2026
Merged

fix: install system dependencies even on a cache hit#1
Ch4s3 merged 2 commits into
mainfrom
fix/install-runtime-deps-on-cache-hit

Conversation

@Ch4s3

@Ch4s3 Ch4s3 commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

The bug

march dynamically links against libblake3, libbrotli, libzstd and llvm.
Those install into system paths (/usr/local/lib, /usr/lib/...) — which are
outside the cached path (~/.local/march).

The Install system dependencies step was gated on
steps.cache.outputs.cache-hit != 'true'. So on a cache hit the action
restored the march binary but never installed the shared objects it needs.
Every invocation then failed with exit 127 — the loader cannot resolve a
needed .so, which bash surfaces as "command not found".

Why it was confusing

The failure is self-inflicted and delayed. The cold-cache run installs the
deps, builds march, passes — and populates the cache. The next run gets a
cache hit, skips the deps, and breaks. It then stays broken until the cache is
evicted.

Observed in march-lean CI:

run cache deps installed result
PR #6 miss yes ✅ pass — and saved the cache
main (push, 8 min later) hit no ❌ fail
PR #8 hit no ❌ fail

Every one of the 171 corpus files errored with
--emit-core-ast produced zero bytes; march exit=127, so the conformance
harness reported MATCH: 0 / ERROR: 171. main has been red since.

The fix

Ungate the step so system dependencies install on every run, and document why
it must stay ungated — the comment is the point, so this doesn't get
"optimized" back into a gate.

The genuinely expensive work stays cached: Set up OCaml and the ~2.5 min
Clone, build, and install March remain gated on cache miss. Cached runs pay
only the apt install plus the blake3 compile.

Clone blake3 into a fresh mktemp -d rather than a fixed /tmp/blake3: now
that the step is unconditional it can run twice in a job that invokes the action
more than once, and git clone into an existing non-empty directory is a hard
error. A unique directory per run cannot collide, so nothing needs deleting —
and it matches the idiom the Clone, build, and install March step already
uses. The cd is guarded (shellcheck SC2164).

Verification

  • action.yml parses as valid YAML.

  • Step gating after the change — only build-only steps remain gated:

    step gate
    Resolve march ref to SHA always
    Restore March build cache always
    Set up OCaml cache miss
    Install system dependencies always ← changed
    Clone, build, and install March cache miss
    Add March to PATH always
    Set outputs always

The real end-to-end proof is a march-lean CI run that gets a cache hit
and still passes — worth confirming after merge, since that's the exact path
that is broken today.

Possible follow-up

A future optimization could split true runtime libraries from build-only -dev
packages, or cache the system libs alongside the binary. I kept this change
minimal and obviously-correct rather than clever, given main is currently red.

Ch4s3 added 2 commits July 23, 2026 08:00
The march binary dynamically links against libblake3, libbrotli, libzstd
and llvm. Those install into system paths (/usr/local/lib, /usr/lib/...),
which are OUTSIDE the cached path (~/.local/march). Gating the
'Install system dependencies' step on cache-hit != 'true' therefore
restored the binary on a cache hit without the shared objects it needs,
and every march invocation failed with exit 127 — the loader cannot
resolve a needed .so, which bash reports as 'command not found'.

The failure is self-inflicted and delayed, which is what made it
confusing: the cold-cache run passes AND populates the cache, so the
NEXT run breaks and stays broken until the cache is evicted. Observed in
march-lean CI: the run that populated the cache passed, then main and
every subsequent PR failed with all 171 corpus files erroring
('--emit-core-ast produced zero bytes; march exit=127').

Ungate the step and document why it must stay ungated. The expensive
work — OCaml setup and the ~2.5 min March build — remains cache-gated,
so cached runs stay fast. Also rm -rf the blake3 clone dir first, since
an unconditional step may now run twice in a job that invokes the action
more than once.

Verified: action.yml parses; only 'Set up OCaml' and 'Clone, build, and
install March' remain cache-gated, both genuinely build-only.
…xed path

Review feedback: rm -rf on a fixed /tmp/blake3 is a blunt way to make an
unconditional step re-runnable. A unique mktemp -d per run cannot collide
in the first place, so there is nothing to delete — and it matches the
idiom the 'Clone, build, and install March' step below already uses.

Also guard the cd (shellcheck SC2164). bash -e would abort the step
anyway, but being explicit costs nothing.
@Ch4s3
Ch4s3 merged commit 16c53b2 into main Jul 23, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant