documentation
Storage And Local State
This page documents where Opal stores local state, how per-job workspaces are prepared, how cache and artifacts are laid out on disk, and how project-level `.opal` content interacts with global configuration.
#Storage And Local State
This page documents where Opal stores local state, how per-job workspaces are prepared, how cache and artifacts are laid out on disk, and how project-level .opal content interacts with global configuration.
#Data root
Opal stores runtime state under your XDG data root at $XDG_DATA_HOME/opal.
Resolution rules:
- If
XDG_DATA_HOMEis set, Opal uses$XDG_DATA_HOME/opal. - If
XDG_DATA_HOMEis unset, Opal defaults to:
~/.local/share/opal#Directory layout
Under the data root, Opal stores:
$XDG_DATA_HOME/opal/
├─ <run-id>/
│ ├─ logs/
│ ├─ scripts/
│ ├─ workspaces/
│ └─ <job-slug>/
│ ├─ artifacts/
│ └─ dependencies/
│ └─ runtime/
├─ cache/
├─ resource-groups/
└─ history.jsonImportant paths:
- per-run session root:
$XDG_DATA_HOME/opal/<run-id>/
- logs for a run:
$XDG_DATA_HOME/opal/<run-id>/logs/
- generated shell scripts for a run:
$XDG_DATA_HOME/opal/<run-id>/scripts/
- copied per-job workspaces:
$XDG_DATA_HOME/opal/<run-id>/workspaces/<job-slug>/
- per-job runtime inspection summaries:
$XDG_DATA_HOME/opal/<run-id>/<job-slug>/runtime/inspect.txt
- persistent local cache root:
$XDG_DATA_HOME/opal/cache/
- cross-run local resource-group locks:
$XDG_DATA_HOME/opal/resource-groups/
- pipeline history database:
$XDG_DATA_HOME/opal/history.json
#Workspace preparation
Opal prepares a per-job workspace snapshot from your current working tree.
What this means:
- Opal does not force a fresh Git clone/fetch/clean cycle for each local job.
- Dirty tracked edits in your current repo are included.
- The repository
.gitdirectory is copied too, so Git-aware local behavior still works. - For linked worktree checkouts where
.gitis a gitdir pointer file, Opal skips copying that pointer. - Opal does not create synthetic Git commits while preparing job workspaces.
What gets filtered out:
- Git-ignored paths, including nested ignore rules
- generated/runtime-heavy directories such as:
target/tests-temp/.opal/node_modules/.svelte-kit/.wrangler/.output/.vercel/.netlify/build/
This is intentional. Opal is meant to run your pipeline against the working tree you are actively editing, while still avoiding obvious local junk.
#Artifacts
Artifacts are stored per job under the current run session.
Layout:
$XDG_DATA_HOME/opal/<run-id>/<job-slug>/artifacts/Behavior:
artifacts.pathsare copied into that directory after the job completes.artifacts.excludeis applied while collecting declared artifact paths.artifacts.untrackedis collected from the copied job workspace.artifacts:reports:dotenvis copied into the same artifact tree and later reloaded where supported.
Dependency staging:
- downstream jobs do not write directly into another job’s artifact directory
- Opal stages dependency artifacts under:
$XDG_DATA_HOME/opal/<run-id>/<job-slug>/dependencies/and mounts or stages only the subset needed by the consumer job.
#Cache
Persistent cache data lives under:
$XDG_DATA_HOME/opal/cache/Behavior:
- each resolved cache key gets its own directory under the cache root
fallback_keysare checked in order when the primary key is missingkey:filesandkey:prefixare resolved against the workspace snapshot
Policy behavior:
pull- restores into a staged per-job cache location
- job writes do not mutate the persistent shared cache entry
push- prepares a writable persistent cache entry for upload/update only
pull-push- restores from the persistent key or fallback and then writes back to that persistent entry
Per-job staging also uses:
$XDG_DATA_HOME/opal/<run-id>/cache-staging/for staged pull-only cache copies.
#Toolchain homes in language images
Be careful when redirecting language toolchain homes into the project workspace.
For Rust images in particular:
CARGO_HOMEis commonly safe to point at"$CI_PROJECT_DIR/.cargo"when you want a local registry and crate cache.RUSTUP_HOMEis different: official Rust images already contain a toolchain under the image default rustup location.- If you override
RUSTUP_HOMEto something like"$CI_PROJECT_DIR/.rustup", you can accidentally hide the bundled toolchain fromrustc,cargo, andrustup.
Why this often appears only after a tag or new branch:
- many pipelines key caches from
CI_COMMIT_REF_SLUG - GitLab defines
CI_COMMIT_REF_SLUGfrom the current branch or tag ref name - a new branch or tag therefore starts with a cold cache unless another run already populated it
- if that cold cache also becomes your new
RUSTUP_HOME, the container can suddenly fail with:
error: rustup could not choose a version of rustc to runRecommended pattern for Rust images:
- leave
RUSTUP_HOMEunset unless you intentionally bootstrap a toolchain into that custom location - cache
CARGO_HOMEandtarget/ - if you do need a custom
RUSTUP_HOME, run an explicit bootstrap step such asrustup default stableor your pinned toolchain install before callingrustcorcargo
#History
Opal records completed runs in:
$XDG_DATA_HOME/opal/history.jsonEach history entry records:
- run id
- finished timestamp
- pipeline status
- per-job:
- name
- stage
- status
- log hash
- log path (when available)
- artifact directory
- artifact list
- cache metadata
- main job container name (when recorded)
- service network name (when recorded)
- service container names (when recorded)
This is what powers opal view and the run-history sidebar in the TUI.
#AI analysis output
When AI troubleshooting is enabled and analysis saving is turned on:
[ai]
save_analysis = trueOpal stores saved AI output under:
$XDG_DATA_HOME/opal/<run-id>/<job-slug>/analysis/Current backend-specific filenames include:
ollama.mdclaude.mdcodex.md
This is where the final saved analysis text is written after a successful provider run.
#Runtime object cleanup
By default, Opal cleans up runtime objects after successful job completion:
- main job containers
- service containers
- per-job service networks
You can override that with config:
[engine]
preserve_runtime_objects = trueWhen enabled, Opal keeps those runtime objects for post-run inspection and records their names into job history so they can be surfaced in opal view.
Opal also writes a normalized runtime summary file per job at:
$XDG_DATA_HOME/opal/<run-id>/<job-slug>/runtime/inspect.txtThat file is intended to be the easiest single place to inspect the recorded runtime/container details from opal view.
#Resource groups
Local resource_group locking is stored under:
$XDG_DATA_HOME/opal/resource-groups/This is how Opal serializes matching jobs across separate local runs on the same machine.
#Project-level .opal directory
Inside a repository, .opal/ is used for project-scoped Opal inputs.
Supported project-level files today:
.opal/config.toml- project-local runtime/config overrides
.opal/env/- preferred secret directory
.opal/envas a file- supported as dotenv-style secret input
Legacy compatibility:
- Opal still supports direct secret files under
.opal/when their filenames are valid environment variable names. .opal/env/takes precedence over those legacy direct.opal/secret files.
#Configuration precedence
Opal loads and merges configuration from these paths in order:
$XDG_CONFIG_HOME/opal/config.toml<workdir>/.opal/config.tomlLater entries override earlier ones.
That means:
$XDG_CONFIG_HOME/opal/config.tomlis the broad user default layer- project-level
.opal/config.tomlestablishes repo defaults and can override global defaults
This is the mechanism that lets tests or local runners inject dynamic global config without editing project config files.