Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
0aef927
fix: add claude wrapper to skip sandbox startup modals
robbycochran Jun 8, 2026
0b72832
fix: pre-seed claude.json onboarding state to skip all startup modals
robbycochran Jun 8, 2026
3bbd22a
feat: stamp CLI version from git tag in dev builds
robbycochran Jun 8, 2026
a92ed86
feat: single registry, PR image builds, version-stamped runner
robbycochran Jun 8, 2026
1113d8d
feat: version-matched sandbox images, drop hardcoded image from agents
robbycochran Jun 8, 2026
49138eb
fix: dev builds fall back to floating image tags
robbycochran Jun 8, 2026
89387cf
fix: create command skips preflight for registered providers, fix test
robbycochran Jun 8, 2026
549383b
fix: wrapper points to correct binary path, fix permissions
robbycochran Jun 8, 2026
2103499
fix: remove invalid companyAnnouncements field from settings.json
robbycochran Jun 8, 2026
1666780
fix: eliminate all Claude Code startup banners in sandbox
robbycochran Jun 8, 2026
7333a8a
refactor: consolidate test targets, auto-detect CI mode
robbycochran Jun 8, 2026
fe27ed7
feat: eliminate floating image tags, version-pin everything
robbycochran Jun 8, 2026
7af2e4b
fix: use commit sha for image tags instead of pr- prefix
robbycochran Jun 8, 2026
6ffb800
fix: dev builds use exact version tag, not last release
robbycochran Jun 8, 2026
299fe67
feat: add make all target (cli + dev-sandbox)
robbycochran Jun 8, 2026
b40436d
feat: use podman by default, CONTAINER_CLI=docker to switch
robbycochran Jun 8, 2026
38d6652
fix: restore ANTHROPIC_API_KEY in agent env, remove apiKeyHelper
robbycochran Jun 8, 2026
ca998c2
fix: add skipApiKeyPrompt to suppress custom API key confirmation
robbycochran Jun 8, 2026
ce95840
fix: pre-approve API key in claude.json to skip confirmation prompt
robbycochran Jun 8, 2026
a513cac
feat: bump openshell to 0.0.58, multi-arch dev-push with podman
robbycochran Jun 8, 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
27 changes: 11 additions & 16 deletions .github/workflows/images.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@ on:
branches: [main]
tags: ["v*"]
pull_request:
paths:
- 'sandbox/**'
- 'build/**'

permissions:
contents: read
Expand All @@ -34,19 +31,16 @@ jobs:
with:
images: ${{ env.IMAGE_BASE }}
tags: |
type=raw,value=sandbox,enable={{is_default_branch}}
type=semver,pattern=sandbox-v{{version}}
type=ref,event=pr,prefix=sandbox-pr-
type=sha,prefix=sandbox-,format=short
- uses: docker/build-push-action@v6
with:
context: sandbox
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' }}
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: |
type=registry,ref=${{ env.IMAGE_BASE }}:sandbox-cache
type=registry,ref=${{ env.IMAGE_BASE }}:sandbox
cache-from: type=registry,ref=${{ env.IMAGE_BASE }}:sandbox-cache
cache-to: type=registry,ref=${{ env.IMAGE_BASE }}:sandbox-cache,mode=max

runner:
Expand All @@ -57,7 +51,11 @@ jobs:
with:
go-version-file: go.mod
- name: Build harness binary
run: CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o build/runner/harness .
run: |
VERSION=$(git describe --tags --always 2>/dev/null || echo dev)
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
-ldflags "-s -w -X main.version=${VERSION}" \
-o build/runner/harness .
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
Expand All @@ -69,17 +67,14 @@ jobs:
with:
images: ${{ env.IMAGE_BASE }}
tags: |
type=raw,value=runner,enable={{is_default_branch}}
type=semver,pattern=runner-v{{version}}
type=ref,event=pr,prefix=runner-pr-
type=sha,prefix=runner-,format=short
- uses: docker/build-push-action@v6
with:
context: build/runner
platforms: linux/amd64
push: ${{ github.event_name != 'pull_request' }}
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: |
type=registry,ref=${{ env.IMAGE_BASE }}:runner-cache
type=registry,ref=${{ env.IMAGE_BASE }}:runner
cache-from: type=registry,ref=${{ env.IMAGE_BASE }}:runner-cache
cache-to: type=registry,ref=${{ env.IMAGE_BASE }}:runner-cache,mode=max
8 changes: 4 additions & 4 deletions .github/workflows/integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ jobs:
done
openshell gateway list

- name: Run CI local
run: make ci-local
- name: Run local integration
run: make test-local

- name: Export gateway logs
if: always()
Expand Down Expand Up @@ -69,8 +69,8 @@ jobs:
- name: Install helm
uses: azure/setup-helm@v4

- name: Run CI kind
run: make ci-kind
- name: Run kind integration
run: make test-kind

- name: Export logs
if: always()
Expand Down
130 changes: 59 additions & 71 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,65 +1,64 @@
## OpenShell Harness — build, push, and test
##
## CI targets (no credentials, GHA-safe):
## make ci # unit + bats + lint (~2min)
## make ci-local # ci + local gateway integration
## make ci-kind # ci + kind (self-contained cluster)
##
## Developer targets (credentials available):
## make dev-test-local # pre-commit: unit + bats + local full + ci
## make dev-test-kind # kind: self-contained lifecycle
## make dev-test-remote # OCP: needs KUBECONFIG
## make dev-test-all # all of the above
## Tests (CI mode auto-detects from CI env var):
## make test # unit + bats + lint (~2min)
## make test-local # test + local gateway integration
## make test-kind # test + kind (self-contained cluster)
## make test-remote # test + OCP (needs KUBECONFIG)
## make test-all # all of the above
##
## Images:
## make sandbox # build + push sandbox (multi-arch)
## make runner # build + push runner

REGISTRY ?= ghcr.io/robbycochran/harness-openshell
DEV_REGISTRY ?= quay.io/rcochran/openshell
DEV_TAG := dev-$(shell git rev-parse --short HEAD)
CONTAINER_CLI ?= podman
PLATFORM := linux/amd64
VERSION := $(shell git describe --tags --always 2>/dev/null || echo dev)
LDFLAGS := -s -w -X main.version=$(VERSION)

SANDBOX_IMAGE := $(REGISTRY):sandbox
RUNNER_IMAGE := $(REGISTRY):runner
DEV_SANDBOX_IMAGE := $(DEV_REGISTRY):$(DEV_TAG)-sandbox
DEV_RUNNER_IMAGE := $(DEV_REGISTRY):$(DEV_TAG)-runner
DEV_SANDBOX_IMAGE := $(REGISTRY):sandbox-$(VERSION)
DEV_RUNNER_IMAGE := $(REGISTRY):runner-$(VERSION)

.PHONY: cli sandbox push-sandbox cli-runner runner push-runner \
vet lint ci ci-local ci-kind \
dev-test-local dev-test-kind dev-test-remote dev-test-all \
dev-sandbox dev-runner clean help
.PHONY: all cli sandbox push-sandbox cli-runner runner push-runner \
vet lint test test-local test-kind test-remote test-all \
dev-sandbox dev-runner dev-push clean help

## ── CLI ──────────────────────────────────────────────────────────────

## Build CLI + sandbox image for local dev
all: cli dev-sandbox

## Build the harness CLI binary
cli:
CGO_ENABLED=0 go build -o harness .
@echo "Built: ./harness"
CGO_ENABLED=0 go build -ldflags '$(LDFLAGS)' -o harness .
@echo "Built: ./harness ($(VERSION))"

## ── Images ────────────────────────────────────────────────────────────

## Sandbox image (Claude Code + mcp-atlassian + gws, multi-arch)
sandbox: sandbox/Dockerfile sandbox/startup.sh \
sandbox/policy.yaml sandbox/CLAUDE.md sandbox/settings.json
docker buildx build --platform linux/amd64,linux/arm64 -t $(SANDBOX_IMAGE) sandbox/ --push
@echo "Built and pushed: $(SANDBOX_IMAGE) (multi-arch)"
-$(CONTAINER_CLI) manifest rm $(SANDBOX_IMAGE) 2>/dev/null
$(CONTAINER_CLI) build --platform linux/amd64 --manifest $(SANDBOX_IMAGE) sandbox/
$(CONTAINER_CLI) build --platform linux/arm64 --manifest $(SANDBOX_IMAGE) sandbox/
@echo "Built: $(SANDBOX_IMAGE) (multi-arch)"

push-sandbox: sandbox
@echo "Already pushed by buildx"
$(CONTAINER_CLI) manifest push $(SANDBOX_IMAGE)

## Cross-compile harness binary for the runner image
cli-runner:
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o build/runner/harness .
@echo "Built: build/runner/harness"
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags '$(LDFLAGS)' -o build/runner/harness .
@echo "Built: build/runner/harness ($(VERSION))"

## Runner image (harness binary + openshell CLI)
runner: cli-runner build/runner/Dockerfile
docker build --platform $(PLATFORM) -t $(RUNNER_IMAGE) build/runner/
$(CONTAINER_CLI) build --platform $(PLATFORM) -t $(RUNNER_IMAGE) build/runner/
@echo "Built: $(RUNNER_IMAGE)"

push-runner: runner
docker push $(RUNNER_IMAGE)
$(CONTAINER_CLI) push $(RUNNER_IMAGE)

## ── Lint targets ─────────────────────────────────────────────────────

Expand All @@ -76,69 +75,58 @@ lint:
$(MAKE) vet; \
fi

## ── CI targets (no credentials, GHA-safe) ────────────────────────────
## ── Test targets ──────────────────────────────────────────────────────
## CI mode auto-detects from the CI env var (set by GitHub Actions).
## Locally: full tests with credentials. On GHA: no-credential mode.

## Unit tests + bats + lint (fast, ~2min, no gateway needed)
ci: vet
test: vet
CGO_ENABLED=0 go test ./...
bats test/preflight.bats

## CI + local gateway integration (ci mode, no credentials)
ci-local: cli ci
./test/test-flow.sh local --ci

## CI + kind gateway integration (self-contained, isolated kubeconfig)
ci-kind: cli ci
./test/kind-lifecycle.sh --ci

## ── Developer targets (credentials available) ────────────────────────
## Local gateway integration
test-local: cli test
./test/test-flow.sh local

## Pre-commit local: unit + bats + local full + local CI
## Requires: openshell gateway running locally, provider credentials.
dev-test-local: cli ci
@echo ""
@echo "=== Integration: local (full) ==="
./test/test-flow.sh local --full
@echo ""
@echo "=== Integration: local (ci) ==="
./test/test-flow.sh local --ci

## Kind: unit + bats + kind full (self-contained lifecycle)
## Creates/destroys its own kind cluster. Never touches your OCP kubectl context.
## Kind: self-contained cluster lifecycle
## Builds sandbox image locally and pre-loads into kind (no registry push needed).
## Use KEEP=1 to keep the cluster after tests (for debugging).
dev-test-kind: cli ci
docker build -t $(DEV_SANDBOX_IMAGE) sandbox/
test-kind: cli test
$(CONTAINER_CLI) build -t $(DEV_SANDBOX_IMAGE) sandbox/
@echo ""
SANDBOX_IMAGE=$(DEV_SANDBOX_IMAGE) ./test/kind-lifecycle.sh $(if $(KEEP),--keep)

## Remote (OCP): unit + bats + OCP full + OCP CI
## Requires: KUBECONFIG set, provider credentials.
## Builds dev images to quay.io (OCP can't pull private ghcr.io).
dev-test-remote: cli ci dev-sandbox dev-runner
## Remote (OCP): requires KUBECONFIG set
test-remote: cli test dev-sandbox dev-runner
@test -n "$${KUBECONFIG}" || { echo "ERROR: Set KUBECONFIG for OCP (e.g. export KUBECONFIG=infracluster/kubeconfig)"; exit 1; }
@echo ""
@echo "=== Integration: OCP (full) ==="
SANDBOX_IMAGE=$(DEV_SANDBOX_IMAGE) RUNNER_IMAGE=$(DEV_RUNNER_IMAGE) ./test/test-flow.sh ocp --full
@echo ""
@echo "=== Integration: OCP (ci) ==="
RUNNER_IMAGE=$(DEV_RUNNER_IMAGE) ./test/test-flow.sh ocp --ci
SANDBOX_IMAGE=$(DEV_SANDBOX_IMAGE) RUNNER_IMAGE=$(DEV_RUNNER_IMAGE) ./test/test-flow.sh ocp

## All: local + kind + remote
dev-test-all: dev-test-local dev-test-kind dev-test-remote
test-all: test-local test-kind test-remote

## ── Dev image builds ─────────────────────────────────────────────────

## Build dev sandbox image to quay.io (multi-arch)
## Build dev sandbox image locally (native arch only)
dev-sandbox:
docker buildx build --platform linux/amd64,linux/arm64 -t $(DEV_SANDBOX_IMAGE) sandbox/ --push
@echo "Built and pushed: $(DEV_SANDBOX_IMAGE)"
$(CONTAINER_CLI) build -t $(DEV_SANDBOX_IMAGE) sandbox/
@echo "Built: $(DEV_SANDBOX_IMAGE)"

## Build dev runner image to quay.io
## Build dev runner image locally
dev-runner: cli-runner
docker build --platform $(PLATFORM) -t $(DEV_RUNNER_IMAGE) build/runner/
docker push $(DEV_RUNNER_IMAGE)
@echo "Built and pushed: $(DEV_RUNNER_IMAGE)"
$(CONTAINER_CLI) build --platform $(PLATFORM) -t $(DEV_RUNNER_IMAGE) build/runner/
@echo "Built: $(DEV_RUNNER_IMAGE)"

## Build and push dev images (sandbox: multi-arch, runner: amd64)
dev-push: cli-runner
-$(CONTAINER_CLI) rmi $(DEV_SANDBOX_IMAGE) 2>/dev/null
-$(CONTAINER_CLI) manifest rm $(DEV_SANDBOX_IMAGE) 2>/dev/null
$(CONTAINER_CLI) build --platform linux/amd64 --manifest $(DEV_SANDBOX_IMAGE) sandbox/
$(CONTAINER_CLI) build --platform linux/arm64 --manifest $(DEV_SANDBOX_IMAGE) sandbox/
$(CONTAINER_CLI) manifest push $(DEV_SANDBOX_IMAGE)
$(CONTAINER_CLI) build --platform $(PLATFORM) -t $(DEV_RUNNER_IMAGE) build/runner/
$(CONTAINER_CLI) push $(DEV_RUNNER_IMAGE)
@echo "Pushed: $(DEV_SANDBOX_IMAGE) (multi-arch) $(DEV_RUNNER_IMAGE) (amd64)"

## ── Convenience targets ───────────────────────────────────────────────

Expand Down
24 changes: 20 additions & 4 deletions SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ Agent configs live in `agents/*.yaml`. Each declares the sandbox image, entrypoi

```yaml
name: agent
image: ghcr.io/robbycochran/harness-openshell:sandbox
entrypoint: claude --bare
entrypoint: claude
tty: true

providers:
Expand All @@ -36,8 +35,8 @@ env:

Fields:
- `name` (required) -- sandbox name, used for `openshell sandbox connect`
- `image` -- container image for the sandbox
- `entrypoint` -- command to run (default: `claude --bare`)
- `image` -- container image for the sandbox (default: version-matched from ghcr.io, override with `SANDBOX_IMAGE` env)
- `entrypoint` -- command to run (default: `claude`)
- `tty` -- enable TTY (default: false)
- `task` -- path to a task.md file, passed as argument to entrypoint
- `providers` -- list of provider profile references
Expand Down Expand Up @@ -105,6 +104,23 @@ In-cluster command for the runner Job. Reads agent config from `/etc/openshell/s
| `build/runner/Dockerfile` | Runner image: harness binary + openshell CLI |
| `sandbox/policy.yaml` | Network egress rules applied to sandboxes |

## Image Tags

All images are published to `ghcr.io/robbycochran/harness-openshell`. No floating tags (`:latest`, `:sandbox`, `:runner`) are used.

| Trigger | Sandbox | Runner |
|---------|---------|--------|
| Release `v0.1.2` | `:sandbox-v0.1.2` | `:runner-v0.1.2` |
| Any push/PR | `:sandbox-<sha>` | `:runner-<sha>` |

The CLI resolves images from its embedded version (set via `-ldflags` at build time):

- `v0.1.2` → `:sandbox-v0.1.2` (tagged release)
- `v0.1.2-5-gabc1234` → `:sandbox-v0.1.2-5-gabc1234` (dev build, matches `make dev-sandbox`)
- `dev` → `:sandbox` (bare `go build` without ldflags)

`SANDBOX_IMAGE` and `RUNNER_IMAGE` env vars override the version-based resolution.

## Environment Variables

| Variable | Purpose |
Expand Down
3 changes: 1 addition & 2 deletions agents/default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
# harness up --agent research # uses agents/research.yaml

name: agent
image: ghcr.io/robbycochran/harness-openshell:sandbox
entrypoint: claude --bare
entrypoint: claude
tty: true

providers:
Expand Down
3 changes: 1 addition & 2 deletions agents/demo.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
# harness up --local --agent demo

name: demo
image: ghcr.io/robbycochran/harness-openshell:sandbox
entrypoint: claude --bare -p
entrypoint: claude -p
task: demo/DEMO-TASK.md
tty: false

Expand Down
2 changes: 1 addition & 1 deletion build/runner/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ FROM registry.access.redhat.com/ubi9/ubi-minimal:latest

RUN microdnf install -y openssh-clients tar gzip --nodocs && microdnf clean all

ARG OPENSHELL_VERSION=0.0.57
ARG OPENSHELL_VERSION=0.0.58
RUN curl -LsSf "https://github.com/NVIDIA/OpenShell/releases/download/v${OPENSHELL_VERSION}/openshell-x86_64-unknown-linux-musl.tar.gz" \
| tar xz -C /usr/local/bin openshell

Expand Down
Loading
Loading