This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
A composite GitHub Action that resolves pass://vault/item/field URI references from a Proton Pass vault and exposes them as step outputs or env vars. There is no compiled application — the action is glue around the official pass-cli binary plus bash scripts in scripts/.
# Lint every bash script
shellcheck scripts/*.sh tests/*.sh
# Run the bash test suite directly (uses the mock pass-cli; no Proton account needed)
bash tests/run-local-tests.sh
# Simulate the full GitHub workflow locally with the official Actions runner
npx @redwoodjs/agent-ci run --workflow tests/test-workflow.ymlThere is no single-test selector; run-local-tests.sh runs all numbered tests sequentially. To run one, comment out the others or copy the block.
The action is defined in action.yml as 5 composite steps that run in order:
- install-cli.sh — installs
pass-clito~/.local/binand appends to$GITHUB_PATH. Uses Proton'sinstall.shforlatest, or downloads a pinned binary and verifies againstversions.jsonSHA-256 when a version is specified. - Authenticate (inline in action.yml) — runs
pass-cli loginnon-interactively, readingPROTON_PASS_PERSONAL_ACCESS_TOKENfrom env. Thenpass-cli infoconfirms the token resolved to a session.PROTON_PASS_KEY_PROVIDER=fsis required so the session persists across steps. - resolve-secrets.sh — iterates over
env(), regex-matches^pass://(.+)/(.+)/(.+)$, callspass-cli viewfor each, and writes results to$GITHUB_ENVusing random heredoc delimiters (EOF_<hex>) so multiline secrets (e.g. SSH keys) survive intact. Masks each line individually via::add-mask::so multiline values are fully redacted. Env-var-only — composite actions can't emit dynamic step outputs, so we don't try. - inject-template.sh — only runs if
env-templateinput is set. Delegates{{ pass://... }}substitution topass-cli inject -i <template> -o <output>. Output path strips.template/.tpl, else appends.resolved. Masks values by re-reading the resolved file. - cleanup.sh — runs with
if: always(). Callspass-cli logoutand removes~/.local/share/proton-pass-cli/.session/.
Key cross-cutting points:
- The action reads
pass://URIs from its own step'senv:block, not from inputs. That's why callers setenv: KEY: "pass://..."on the action step. - Session state flows between steps via the filesystem (
PROTON_PASS_KEY_PROVIDER=fs). Every script that touches pass-cli must export this env var. - Masking is opt-out (
mask-values: truedefault). When disabled in tests, secret values appear in logs.
tests/mock-pass-cli.sh is the test double for the real pass-cli. It pattern-matches the URI in pass-cli view and returns deterministic strings (mock-db-password-12345, etc.). The mock is installed onto PATH ahead of the real binary by run-local-tests.sh. For workflow-level simulation, tests/test-workflow.yml copies the mock onto the runner's /usr/local/bin/pass-cli before invoking the action.
When adding a new test:
- Use
env -i PATH=... HOME=... ...to give resolve-secrets.sh a clean env — otherwise stray vars from your shell leak into the resolver. - Mock new URI shapes by adding a case branch in
tests/mock-pass-cli.sh. - Run
shellcheck scripts/*.sh tests/*.shto catch regressions before pushing.
- The action is consumed via
uses: gizmodlabs/load-secrets-proton-pass@v1— changes toaction.ymlinputs are breaking unless defaults preserve old behavior. pass-clirequires Proton Pass Plus+; tests must use the mock.- The URI regex is greedy (
.+/.+/.+); vault/item/field names containing/will misparse.