Skip to content

Commit 8e0414c

Browse files
committed
refactor(sandbox-kit): publish per kit, and only on a version change (PFM-7373)
Review follow-ups. Publish every kit under devel/sandbox-kit rather than the hardcoded claude one: a discover job derives the matrix from the directories that hold a spec.yaml, so a new kit needs no workflow edit. Narrow the trigger to devel/sandbox-kit/*/spec.yaml. The previous devel/sandbox-kit/** also matched the README, so a docs edit republished the released tag with fresh content under the same immutable version. Guard the same hazard from the other side by skipping a version already present in the registry, since the trigger still fires on spec edits that leave the version alone. Make a zero-match glob fatal in the bump script. It previously ran from the invocation directory and swallowed a miss, so a release run from the wrong place would leave every spec at the old version and publish nothing, with no error. Move checkout and the version read ahead of the tooling installs so a missing version fails in seconds rather than after a 95MB download. Drop the wrapper's run_plain indirection and the detected flag left over from two-mode dispatch, along with the last stale references to the removed trace mode. Assisted-by: Claude Code Signed-off-by: Miguel Martinez <miguel@chainloop.dev> Chainloop-Trace-Sessions: 7da50f6b-428c-4749-82b5-562ae1ba890e
1 parent 37d6165 commit 8e0414c

5 files changed

Lines changed: 86 additions & 48 deletions

File tree

‎.github/workflows/package_sandbox_kit.yaml‎

Lines changed: 68 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,50 @@
11
name: Package Sandbox Kit
22

33
on:
4-
# Only push the kit if the kit sources have changed. The release bump PR
5-
# rewrites each spec.yaml `version:`, so merging it is what triggers a publish
6-
# — the same shape as package_chart.yaml reacting to deployment/chainloop/**
4+
# Only the specs, not the README beside them: the release bump PR rewrites each
5+
# spec.yaml `version:`, and that is what should trigger a publish.
76
push:
87
branches:
98
- main
109
paths:
11-
- devel/sandbox-kit/**
10+
- devel/sandbox-kit/*/spec.yaml
1211

1312
permissions: read-all
1413

1514
jobs:
15+
# Every directory under devel/sandbox-kit/ holding a spec.yaml is a kit, so
16+
# adding one needs no change here.
17+
discover:
18+
name: Discover kits
19+
runs-on: ubuntu-latest
20+
permissions:
21+
contents: read
22+
outputs:
23+
kits: ${{ steps.find.outputs.kits }}
24+
steps:
25+
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
26+
with:
27+
persist-credentials: false
28+
- id: find
29+
run: |
30+
kits=$(find devel/sandbox-kit -mindepth 2 -maxdepth 2 -name spec.yaml -printf '%h\n' \
31+
| sed 's|.*/||' | sort | jq -Rsc 'split("\n")[:-1]')
32+
if [[ "${kits}" == "[]" ]]; then
33+
echo "::error::no kits found under devel/sandbox-kit"
34+
exit 1
35+
fi
36+
echo "kits=${kits}" >> $GITHUB_OUTPUT
37+
echo "Publishing: ${kits}"
38+
1639
package:
17-
name: Package and push Sandbox Kit
40+
name: Package and push ${{ matrix.kit }}
41+
needs: discover
1842
runs-on: ubuntu-latest
43+
strategy:
44+
# One kit's failure must not cancel the others mid-publish.
45+
fail-fast: false
46+
matrix:
47+
kit: ${{ fromJSON(needs.discover.outputs.kits) }}
1948
permissions:
2049
contents: read
2150
id-token: write # Docker Hub OIDC login, SLSA provenance and keyless kit signing
@@ -25,9 +54,25 @@ jobs:
2554
# Docker Sandboxes ships Linux packages only on tagged releases, not on
2655
# nightly, so this is pinned to a stable tag and bumped by hand.
2756
SBX_VERSION: "v0.43.0"
28-
KIT_DIR: "devel/sandbox-kit/claude"
29-
KIT_REPO: "docker.io/chainloop/sbx-kit-claude"
57+
KIT_DIR: "devel/sandbox-kit/${{ matrix.kit }}"
58+
KIT_REPO: "docker.io/chainloop/sbx-kit-${{ matrix.kit }}"
3059
steps:
60+
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
61+
with:
62+
persist-credentials: false
63+
64+
- name: Read kit version
65+
id: kit_version
66+
run: |
67+
# The kit's own spec.yaml is the source of truth; bump-chart-and-dagger-version.sh
68+
# keeps it in step with the chart's appVersion on every release.
69+
kit_version=$(yq -r '.version' "${KIT_DIR}/spec.yaml")
70+
if [[ -z "${kit_version}" || "${kit_version}" == "null" ]]; then
71+
echo "::error::${KIT_DIR}/spec.yaml declares no version:"
72+
exit 1
73+
fi
74+
echo "kit_version=${kit_version}" >> $GITHUB_OUTPUT
75+
3176
- name: Install Chainloop
3277
# Deliberately NOT `curl ... | bash`: this job holds an OIDC token that can
3378
# mint Docker Hub credentials and sign artifacts, so the installer is
@@ -64,28 +109,29 @@ jobs:
64109
with:
65110
username: chainloop
66111

67-
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
68-
with:
69-
persist-credentials: false
70-
71-
- name: Read kit version
72-
id: kit_version
112+
- name: Skip if this version is already published
113+
id: published
114+
env:
115+
KIT_VERSION: ${{ steps.kit_version.outputs.kit_version }}
73116
run: |
74-
# The kit's own spec.yaml is the source of truth; bump-chart-and-dagger-version.sh
75-
# keeps it in step with the chart's appVersion on every release.
76-
kit_version=$(yq -r '.version' "${KIT_DIR}/spec.yaml")
77-
if [[ -z "${kit_version}" || "${kit_version}" == "null" ]]; then
78-
echo "::error::${KIT_DIR}/spec.yaml declares no version:"
79-
exit 1
117+
# The trigger fires on any spec.yaml change, not only a version bump, so
118+
# a mid-cycle edit must not overwrite an already-signed, already-attested
119+
# immutable tag with fresh content.
120+
if sbx kit inspect "${KIT_REPO}:${KIT_VERSION}" >/dev/null 2>&1; then
121+
echo "::notice::${KIT_REPO}:${KIT_VERSION} is already published, nothing to do"
122+
echo "skip=true" >> $GITHUB_OUTPUT
123+
else
124+
echo "skip=false" >> $GITHUB_OUTPUT
80125
fi
81-
echo "kit_version=${kit_version}" >> $GITHUB_OUTPUT
82126
83127
- name: Validate kit
128+
if: steps.published.outputs.skip == 'false'
84129
# Fails loudly here rather than halfway through a push if the pinned sbx
85130
# release does not understand something the spec declares.
86131
run: sbx kit validate "./${KIT_DIR}"
87132

88133
- name: Add Attestation (Sandbox Kit) and Push Kit
134+
if: steps.published.outputs.skip == 'false'
89135
run: |
90136
# KIT_VERSION arrives through env, not ${{ }} interpolation, so the value
91137
# is never expanded into this script's source.
@@ -114,12 +160,12 @@ jobs:
114160
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
115161

116162
- name: Finish and Record Attestation
117-
if: ${{ success() }}
163+
if: ${{ success() && steps.published.outputs.skip == 'false' }}
118164
run: |
119165
chainloop attestation push
120166
121167
- name: Mark attestation as failed
122-
if: ${{ failure() }}
168+
if: ${{ failure() && steps.published.outputs.skip == 'false' }}
123169
run: |
124170
chainloop attestation reset
125171

‎.github/workflows/utils/bump-chart-and-dagger-version.sh‎

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,14 @@ if [[ -n "${platform_version}" && "${platform_version}" != "null" ]]; then
6060
fi
6161

6262
## Update the Docker Sandboxes kit versions
63-
# Each kit declares the Chainloop release whose CLI it ships, so they track semVer
64-
# like appVersion does. The path is fixed rather than an argument because this is a
65-
# directory of kits that grows, and `schemaVersion:` is left alone by the ^version anchor.
66-
for kit_spec in devel/sandbox-kit/*/spec.yaml; do
67-
[ -e "${kit_spec}" ] || continue
68-
sed -i "s#^version:.*#version: ${semVer}#g" "${kit_spec}"
63+
# Each kit declares the Chainloop release it belongs to, tracking semVer like
64+
# appVersion does. `schemaVersion:` is left alone by the ^version anchor.
65+
# Matching nothing is an error, not a no-op: a silent skip here would leave the
66+
# specs at the old version and the publish workflow would never fire.
67+
shopt -s nullglob
68+
kit_specs=(devel/sandbox-kit/*/spec.yaml)
69+
[ "${#kit_specs[@]}" -gt 0 ] || die "no kit specs found under devel/sandbox-kit (run from the repo root)"
70+
for kit_spec in "${kit_specs[@]}"; do
71+
sed -i "s#^version:.*#version: ${semVer}#" "${kit_spec}"
6972
done
7073

‎devel/sandbox-kit/README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ inside the sandbox before it is reclaimed.
4545

4646
### 1. Through the environment file
4747

48-
The repo's `sbxenv.yaml` declares the agent, the kit, the clone-mode workspace and the trace mode, so the
48+
The repo's `sbxenv.yaml` declares the agent, the kit and the clone-mode workspace, so the
4949
only thing left to pass is the token:
5050

5151
```bash

‎devel/sandbox-kit/claude/spec.yaml‎

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,10 @@
2222
schemaVersion: "2"
2323
kind: sandbox # REQUIRED: only kind:sandbox may set an entrypoint
2424
name: chainloop-trace-claude
25-
# Tracks the Chainloop release this kit ships the CLI of. Bumped automatically on
26-
# release by .github/workflows/utils/bump-chart-and-dagger-version.sh, alongside the
27-
# chart's appVersion - do not edit by hand.
25+
# The Chainloop release this kit belongs to. Bumped automatically on release by
26+
# .github/workflows/utils/bump-chart-and-dagger-version.sh, alongside the chart's
27+
# appVersion - do not edit by hand. NOTE the install step below is not pinned to
28+
# it, so it is a release marker, not a statement about the CLI inside.
2829
version: v1.109.0
2930
displayName: Claude Code (Chainloop-traced)
3031
description: >-
@@ -87,10 +88,6 @@ setup:
8788
#!/usr/bin/env bash
8889
set -euo pipefail
8990
90-
run_plain() {
91-
exec claude --dangerously-skip-permissions "$@"
92-
}
93-
9491
# Anything that would hand back an untraced agent is fatal: a sandbox
9592
# that quietly records nothing is worse than one that refuses to start.
9693
die() {
@@ -129,16 +126,8 @@ setup:
129126
# Persistent tracing only: the repo must already be initialized, and the
130127
# identity comes from its committed .chainloop.yml.
131128
repo_root=$(git rev-parse --show-toplevel 2>/dev/null || echo "$PWD")
132-
detected=0
133-
for f in "$repo_root/.chainloop.yml" "$repo_root/.chainloop.yaml"; do
134-
if [ -f "$f" ] && grep -qE '^[[:space:]]*projectName:[[:space:]]*["'"'"']?[A-Za-z0-9]' "$f" \
135-
&& grep -q "chainloop trace hook" "$repo_root/.claude/settings.json" 2>/dev/null; then
136-
detected=1
137-
break
138-
fi
139-
done
140-
141-
if [ "$detected" != 1 ]; then
129+
if ! grep -qsE '^[[:space:]]*projectName:[[:space:]]*["'"'"']?[A-Za-z0-9]' "$repo_root"/.chainloop.y*ml \
130+
|| ! grep -qs "chainloop trace hook" "$repo_root/.claude/settings.json"; then
142131
die "this repository is not initialized for chainloop trace.
143132
144133
Run this in the repository first, then start the sandbox again:
@@ -155,7 +144,7 @@ setup:
155144
156145
echo "[chainloop-trace] Repo initialized for chainloop trace - persistent mode" >&2
157146
echo "[chainloop-trace] identity from .chainloop.yml; attestation is pushed on 'git push'" >&2
158-
run_plain "$@"
147+
exec claude --dangerously-skip-permissions "$@"
159148
160149
environment:
161150
variables:

‎sbxenv.yaml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ workspace:
112112
# devel/sandbox-kit/README.md.
113113

114114
# NOTE — deliberately NOT declared here:
115-
# env: nothing left to set. The Chainloop token and the mode reach the
115+
# env: nothing left to set. The Chainloop token reaches the
116116
# sandbox as KIT arguments (above), not as environment variables, so
117117
# the kit owns its own configuration surface. Path B still applies:
118118
# the kit turns chainloopToken into CHAINLOOP_TOKEN inside the VM,

0 commit comments

Comments
 (0)