Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
97615b8
ci: pin actions/checkout to commit SHA
tacaswell Jul 22, 2026
93e9910
ci(lint): guard zero/unfetchable push ref, generate the version table
tacaswell Jul 24, 2026
a7ee8d2
fix(gpg-passwd): close nameref collision;
tacaswell Jul 23, 2026
b35e09f
fix(gpg-passwd): surface eval failures from decrypted content
tacaswell Jul 24, 2026
fb9642d
fix(gh-protect-branch): validate input early; build JSON with jq; pag…
tacaswell Jul 23, 2026
4ef2523
fix(gh-protect-branch): scope ruleset lookups to the repository
tacaswell Jul 24, 2026
442388f
fix(azoidcapp): escape OData filters; fix retry policy, error handlin…
tacaswell Jul 23, 2026
f1b5c1b
fix(azoidcapp): reconcile grant scopes on the already-exists path
tacaswell Jul 24, 2026
9dcbf48
fix(pemdecompose): re-raise fatal signals; align exit codes; cache op…
tacaswell Jul 23, 2026
f05dca6
fix(pemdecompose): check mktemp, halve openssl forks, document --no-c…
tacaswell Jul 24, 2026
b04108f
perf(bwrap): dedupe path & binary-mask resolution
tacaswell Jul 24, 2026
8312f80
fix(bwrap): canonicalize path-safety for symlinked homes
tacaswell Oct 20, 2026
3863857
fix(bwrap): harden path & git-config input handling
tacaswell Oct 20, 2026
391a7c4
feat(bwrap): allow nesting --ro-path under --rw-path to protect subdirs
tacaswell Oct 20, 2026
d224383
fix(bwrap): shell-quote --dry-run output
tacaswell Oct 20, 2026
1260299
fix(bw*): --dry-run no longer mutates host state
tacaswell Oct 20, 2026
f36b7e2
fix(bwclaude,bwopencode): persist plans/; correct plans/ persistence …
tacaswell Oct 20, 2026
be8bab9
fix(bwcodex): eliminate config.toml temp-file leak
tacaswell Oct 20, 2026
ed3d69f
fix(bwcodex,bwcopilot): remove duplicate CODEX_* setenv; remove dead …
tacaswell Oct 20, 2026
e3fd433
refactor(bw*): add forward_env_by_prefix helper; deduplicate prefix s…
tacaswell Oct 20, 2026
2610ce7
fix(bw*): --help no longer requires the tool; fix --exec example and …
tacaswell Oct 20, 2026
365a936
fix(bwcopilot): dry-run fd placeholder for config.json
tacaswell Oct 20, 2026
ef37a2c
fix(bwcodex): mount the SSL_CERT_FILE target
tacaswell Oct 20, 2026
580c415
fix(bwclaude): key on CLAUDE_CONFIG_DIR, stop persisting settings.json
tacaswell Oct 20, 2026
f629652
docs(bw-wrappers): list --github-tokens with the common options
tacaswell Oct 20, 2026
6d7474e
fix(bwrap): inject sandbox secrets via env -i, not bwrap's argv
tacaswell Oct 20, 2026
db52f7c
feat(bwrap): mount personal skills directories
tacaswell Oct 20, 2026
1bd5843
FIX: deal with changes to codex and its desire to launch a daemon
tacaswell Oct 20, 2026
1dd4b28
STY: do not confuse shellcheck
tacaswell Oct 20, 2026
76af27a
MNT: move from pre-commit to prek
tacaswell Oct 20, 2026
ed3b816
feat(bwrap): mount tool authoring directories read-write
tacaswell Oct 20, 2026
e70a583
fix(bwrap): refuse bound directories that contain blocked paths
tacaswell Oct 20, 2026
970726a
fix(bwrap): guard the empty extra-path check against set -u on old bash
tacaswell Oct 20, 2026
223b82c
fix(bwrap): apply binary masks after user mounts
tacaswell Oct 20, 2026
7e85397
docs(gpg-passwd): drop the stale "bash only" warning
tacaswell Oct 20, 2026
2011721
fix(bwrap): keep extra paths containing a newline intact
tacaswell Oct 20, 2026
a1f52f6
fix(bwrap): authoring dirs: handle hidden mounts, dot entries, new homes
tacaswell Oct 20, 2026
b05cfd3
MNT: error if opencode(2) is not configured at all
tacaswell Oct 20, 2026
af7bc7f
feat(bwopencode2): mount the opencode2 directories at the standard lo…
tacaswell Oct 20, 2026
1cd82c8
fix(bwrap): apply binary masks as the very last step in every launcher
tacaswell Oct 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 79 additions & 27 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:

steps:
- name: Checkout code
uses: actions/checkout@v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false
Expand All @@ -36,9 +36,7 @@ jobs:
pixi-version: v0.71.3
cache: false
frozen: true

- name: Install dev environment
run: pixi install -e dev
environments: dev

- name: Determine changed-file ref range
id: refs
Expand All @@ -52,44 +50,98 @@ jobs:
echo "from_ref=origin/${BASE_REF}" >> "$GITHUB_OUTPUT"
echo "to_ref=${SHA}" >> "$GITHUB_OUTPUT"
elif [[ "$EVENT_NAME" == "push" ]]; then
echo "from_ref=${BEFORE}" >> "$GITHUB_OUTPUT"
echo "to_ref=${SHA}" >> "$GITHUB_OUTPUT"
# On branch creation, $BEFORE is 40 zeros; after force-push, the old SHA
# may be unfetchable. Both break prek --from-ref, so fall back to --all-files.
if [[ "$BEFORE" =~ ^0+$ ]] || ! git cat-file -e "$BEFORE" 2>/dev/null; then
echo "from_ref=" >> "$GITHUB_OUTPUT"
echo "to_ref=" >> "$GITHUB_OUTPUT"
else
echo "from_ref=${BEFORE}" >> "$GITHUB_OUTPUT"
echo "to_ref=${SHA}" >> "$GITHUB_OUTPUT"
fi
else
# workflow_dispatch: lint everything
echo "from_ref=" >> "$GITHUB_OUTPUT"
echo "to_ref=" >> "$GITHUB_OUTPUT"
fi

- name: Run pre-commit (changed files)
id: pre_commit
- name: Collect tool versions
id: versions
run: |
# Extract versions from pixi dev environment
RUFF_VER=$(pixi list -e dev --json | jq -r '.[] | select(.name=="ruff") | .version')
MYPY_VER=$(pixi list -e dev --json | jq -r '.[] | select(.name=="mypy") | .version')
CODESPELL_VER=$(pixi list -e dev --json | jq -r '.[] | select(.name=="codespell") | .version')
YAMLLINT_VER=$(pixi list -e dev --json | jq -r '.[] | select(.name=="yamllint") | .version')
ZIZMOR_VER=$(pixi list -e dev --json | jq -r '.[] | select(.name=="zizmor") | .version')

# Extract versions from .pre-commit-config.yaml rev: tags
# (strip wrapper-specific build suffixes where applicable)
PRECOMMIT_HOOKS_VER=$(grep -A1 'repo: https://github.com/pre-commit/pre-commit-hooks' .pre-commit-config.yaml | grep 'rev:' | awk '{print $2}')
MARKDOWNLINT_VER=$(grep -A1 'repo: https://github.com/igorshubovych/markdownlint-cli' .pre-commit-config.yaml | grep 'rev:' | awk '{print $2}')
ACTIONLINT_VER=$(grep -A1 'repo: https://github.com/rhysd/actionlint' .pre-commit-config.yaml | grep 'rev:' | awk '{print $2}')
# shfmt: pre-commit-shfmt adds -N build numbers (v3.12.0-2 -> v3.12.0)
SHFMT_VER=$(grep -A1 'repo: https://github.com/scop/pre-commit-shfmt' .pre-commit-config.yaml | grep 'rev:' | awk '{print $2}' | sed 's/-[0-9]\+$//')
# eratta: shellcheck-py: Python wrapper adds .N to upstream shellcheck version (v0.11.0.1 -> v0.11.0)
SHELLCHECK_VER=$(grep -A1 'repo: https://github.com/shellcheck-py/shellcheck-py' .pre-commit-config.yaml | grep 'rev:' | awk '{print $2}' | sed 's/\.[0-9]\+$//')
GITLEAKS_VER=$(grep -A1 'repo: https://github.com/gitleaks/gitleaks' .pre-commit-config.yaml | grep 'rev:' | awk '{print $2}')

# Write to GITHUB_OUTPUT
{
echo "ruff=$RUFF_VER"
echo "mypy=$MYPY_VER"
echo "codespell=$CODESPELL_VER"
echo "yamllint=$YAMLLINT_VER"
echo "zizmor=$ZIZMOR_VER"
echo "precommit_hooks=$PRECOMMIT_HOOKS_VER"
echo "markdownlint=$MARKDOWNLINT_VER"
echo "actionlint=$ACTIONLINT_VER"
echo "shfmt=$SHFMT_VER"
echo "shellcheck=$SHELLCHECK_VER"
echo "gitleaks=$GITLEAKS_VER"
} >> "$GITHUB_OUTPUT"

- name: Run prek (changed files)
id: prek
env:
FROM_REF: ${{ steps.refs.outputs.from_ref }}
TO_REF: ${{ steps.refs.outputs.to_ref }}
run: |
set -o pipefail
if [[ -n "$FROM_REF" ]]; then
pixi run -e dev pre-commit run \
pixi run -e dev prek run \
--from-ref "$FROM_REF" \
--to-ref "$TO_REF" \
--show-diff-on-failure \
--color always \
2>&1 | tee /tmp/pre-commit-output.txt
2>&1 | tee /tmp/prek-output.txt
else
pixi run -e dev pre-commit run \
pixi run -e dev prek run \
--all-files \
--show-diff-on-failure \
--color always \
2>&1 | tee /tmp/pre-commit-output.txt
2>&1 | tee /tmp/prek-output.txt
fi

- name: Write job summary
if: always()
env:
PRE_COMMIT_OUTCOME: ${{ steps.pre_commit.outcome }}
PREK_OUTCOME: ${{ steps.prek.outcome }}
FROM_REF: ${{ steps.refs.outputs.from_ref }}
TO_REF: ${{ steps.refs.outputs.to_ref }}
RUFF_VER: ${{ steps.versions.outputs.ruff }}
MYPY_VER: ${{ steps.versions.outputs.mypy }}
CODESPELL_VER: ${{ steps.versions.outputs.codespell }}
YAMLLINT_VER: ${{ steps.versions.outputs.yamllint }}
ZIZMOR_VER: ${{ steps.versions.outputs.zizmor }}
PRECOMMIT_HOOKS_VER: ${{ steps.versions.outputs.precommit_hooks }}
MARKDOWNLINT_VER: ${{ steps.versions.outputs.markdownlint }}
ACTIONLINT_VER: ${{ steps.versions.outputs.actionlint }}
SHFMT_VER: ${{ steps.versions.outputs.shfmt }}
SHELLCHECK_VER: ${{ steps.versions.outputs.shellcheck }}
GITLEAKS_VER: ${{ steps.versions.outputs.gitleaks }}
run: |
if [[ "$PRE_COMMIT_OUTCOME" == "success" ]]; then
if [[ "$PREK_OUTCOME" == "success" ]]; then
BADGE="All linting hooks passed"
else
BADGE="One or more linting hooks failed"
Expand All @@ -112,23 +164,23 @@ jobs:
echo ""
echo "| Hook | Tool | Version |"
echo "| ---- | ---- | ------- |"
echo "| trailing-whitespace / end-of-file-fixer / check-* | pre-commit-hooks | v5.0.0 |"
echo "| ruff check + ruff format | ruff | 0.15.8 |"
echo "| mypy | mypy | 2.1.0 |"
echo "| codespell | codespell | 2.4.2 |"
echo "| markdownlint | markdownlint-cli | v0.48.0 |"
echo "| yamllint | yamllint | 1.38.0 |"
echo "| actionlint | actionlint | v1.7.12 |"
echo "| zizmor | zizmor | 1.23.1 |"
echo "| shfmt | shfmt | v3.12.0 |"
echo "| shellcheck | shellcheck | v0.11.0 |"
echo "| gitleaks | gitleaks | v8.30.1 |"
echo "| trailing-whitespace / end-of-file-fixer / check-* | pre-commit-hooks | ${PRECOMMIT_HOOKS_VER} |"
echo "| ruff check + ruff format | ruff | ${RUFF_VER} |"
echo "| mypy | mypy | ${MYPY_VER} |"
echo "| codespell | codespell | ${CODESPELL_VER} |"
echo "| markdownlint | markdownlint-cli | ${MARKDOWNLINT_VER} |"
echo "| yamllint | yamllint | ${YAMLLINT_VER} |"
echo "| actionlint | actionlint | ${ACTIONLINT_VER} |"
echo "| zizmor | zizmor | ${ZIZMOR_VER} |"
echo "| shfmt | shfmt | ${SHFMT_VER} |"
echo "| shellcheck | shellcheck | ${SHELLCHECK_VER} |"
echo "| gitleaks | gitleaks | ${GITLEAKS_VER} |"
echo ""
if [[ -s /tmp/pre-commit-output.txt ]]; then
if [[ -s /tmp/prek-output.txt ]]; then
echo "### Output"
echo ""
echo '```'
sed 's/\x1b\[[0-9;]*m//g' /tmp/pre-commit-output.txt
sed 's/\x1b\[[0-9;]*m//g' /tmp/prek-output.txt
echo '```'
fi
} >> "$GITHUB_STEP_SUMMARY"
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# commit and in CI via the Lint workflow
# (.github/workflows/lint.yml). Tool versions are pinned in
# pixi.toml [feature.dev.dependencies] for the pixi-dispatched hooks
# and via `rev:` for the upstream pre-commit-managed hooks.
# and via `rev:` for the upstream prek-managed hooks.
#
# Linter configs live at the repository root and are auto-discovered
# by each tool by its conventional dotfile name. The only exception is
Expand Down
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ Add those three exports to your `~/.bashrc` or `~/.zshrc` for persistence.

- `bash` >= 4.x (the `bw*` wrappers use `[[ ]]` and arrays)
- `bubblewrap` (`bwrap`) >= 0.4.0 for the `bw*` wrappers
- 0.5.0+ enables `--clearenv`
- 0.6.3+ enables bind-over-ro-bind binary masking
- `gpg(1)` for `gpg-passwd.sh`
- `openssl(1)` for `pemdecompose`
Expand All @@ -92,11 +91,13 @@ Common wrapper options:

| Option | Effect |
| --- | --- |
| `--help`, `-h` | Show wrapper help plus the underlying tool's help |
| `--help`, `-h` | Show wrapper help, then the underlying tool's help when the tool is installed |
| `--dry-run` | Print the `bwrap` command without executing it |
| `--exec CMD` | Run `CMD` inside the sandbox instead of the tool |
| `--exec CMD [ARGS...]` | Run `CMD` inside the sandbox instead of the tool. `CMD` is split on whitespace; following arguments are passed unchanged, e.g. `--exec bash -c 'env \| sort'` |
| `--init-auth` | Persist auth credentials to the host (first-time setup) |
| `--new-session` | Force `bwrap --new-session` (stricter isolation; breaks SIGWINCH) |
| `--ro-path PATH`, `--rw-path PATH` | Mount an existing path read-only / read-write (may be repeated and nested) |
| `--github-tokens` | Forward `GH_TOKEN_*` variables so `gh` can authenticate inside the sandbox (off by default) |

`bwclaude` also supports `--debug`. `bwopencode2` has no `--init-auth` (sign-in
persists in its data directory), always runs OpenCode 2 with `--standalone`
Expand Down
Loading