From ceb503581e87cb7f70649ffa1973f6b706704ee7 Mon Sep 17 00:00:00 2001 From: Robby Cochran Date: Fri, 12 Jun 2026 16:08:25 -0700 Subject: [PATCH 1/6] X-Smart-Branch-Parent: main From 0a056338245a443501b0e8e4be4059470c63a3ec Mon Sep 17 00:00:00 2001 From: Robby Cochran Date: Fri, 12 Jun 2026 16:11:02 -0700 Subject: [PATCH 2/6] fix: use git describe for image tags, add make tag target The images workflow was tagging with bare short SHAs (sandbox-abc1234) which did not match the Makefile's VERSION (git describe output like v0.1.2-12-gabcdef0). Now non-tag pushes tag with the full git describe output so the image tag matches what make cli embeds. Tag pushes still produce clean semver tags (sandbox-v0.2.0). Added make tag v=X.Y.Z convenience target for creating release tags. --- .github/workflows/images.yml | 7 ++++++- Makefile | 9 ++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.github/workflows/images.yml b/.github/workflows/images.yml index 0e3b410..da9c7ce 100644 --- a/.github/workflows/images.yml +++ b/.github/workflows/images.yml @@ -19,6 +19,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 - uses: docker/setup-qemu-action@v3 - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 @@ -26,13 +28,16 @@ jobs: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Compute version tag + id: version + run: echo "version=$(git describe --tags --always 2>/dev/null || echo dev)" >> "$GITHUB_OUTPUT" - uses: docker/metadata-action@v5 id: meta with: images: ${{ env.IMAGE_BASE }} tags: | type=semver,pattern=sandbox-v{{version}} - type=sha,prefix=sandbox-,format=short + type=raw,value=sandbox-${{ steps.version.outputs.version }},enable=${{ github.ref_type != 'tag' }} # Fork PRs can't write to the registry; same-repo PRs push so the # integration (kind) job can pull the image without a local preload. - uses: docker/build-push-action@v6 diff --git a/Makefile b/Makefile index 2b7bbd0..46a789e 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,7 @@ DEV_SANDBOX_IMAGE := $(REGISTRY):sandbox-$(VERSION) .PHONY: all cli \ vet lint test test-local test-kind test-remote test-all \ - dev-sandbox dev-push clean help + dev-sandbox dev-push tag clean help ## ── CLI ────────────────────────────────────────────────────────────── @@ -97,6 +97,13 @@ dev-push: ## ── Convenience targets ─────────────────────────────────────────────── +## Create a release tag (e.g., make tag v=0.2.0) +tag: + @test -n "$(v)" || { echo "Usage: make tag v=0.2.0"; exit 1; } + git tag -a "v$(v)" -m "v$(v)" + @echo "Tagged: v$(v)" + @echo "Push with: git push origin v$(v)" + ## Clean built binaries clean: rm -f harness From d3912bf290cf18f8cb8c182e1ce04cccee0e676a Mon Sep 17 00:00:00 2001 From: Robby Cochran Date: Fri, 12 Jun 2026 16:12:18 -0700 Subject: [PATCH 3/6] fix: make tag shows current version, not creates one --- Makefile | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 46a789e..1a97c59 100644 --- a/Makefile +++ b/Makefile @@ -97,12 +97,9 @@ dev-push: ## ── Convenience targets ─────────────────────────────────────────────── -## Create a release tag (e.g., make tag v=0.2.0) +## Show the current version tag (from git describe) tag: - @test -n "$(v)" || { echo "Usage: make tag v=0.2.0"; exit 1; } - git tag -a "v$(v)" -m "v$(v)" - @echo "Tagged: v$(v)" - @echo "Push with: git push origin v$(v)" + @echo $(VERSION) ## Clean built binaries clean: From 84b38d41269d19fe81acd63d99e83fb8250d46e2 Mon Sep 17 00:00:00 2001 From: Robby Cochran Date: Fri, 12 Jun 2026 20:44:59 -0700 Subject: [PATCH 4/6] refactor: standardize env vars with HARNESS_OS_ and OPENSHELL_ prefixes Harness-specific env vars now use HARNESS_OS_ prefix: HARNESS_OS_DIR (was HARNESS_DIR) HARNESS_OS_IMAGE (was SANDBOX_IMAGE) HARNESS_OS_GATEWAY (was GATEWAY_NAME) HARNESS_OS_PULL_SECRET (was PULL_SECRET) HARNESS_OS_SANDBOX_PULL_SECRET (was SANDBOX_PULL_SECRET) OpenShell runtime vars keep OPENSHELL_ prefix: OPENSHELL_CLI, OPENSHELL_NAMESPACE, OPENSHELL_MODEL, OPENSHELL_CHART_VERSION Also renamed DEV_SANDBOX_IMAGE to IMAGE in Makefile. --- Makefile | 27 +++++++++++++-------------- SPEC.md | 18 ++++++++++-------- TODO.md | 2 +- cmd/deploy.go | 6 +++--- cmd/sandbox_image.go | 4 +--- cmd/up_test.go | 2 +- dev-harness.sh | 2 +- internal/gateway/config.go | 2 +- internal/gateway/config_test.go | 6 +++--- main.go | 2 +- test/kind-lifecycle.sh | 12 ++++++------ 11 files changed, 41 insertions(+), 42 deletions(-) diff --git a/Makefile b/Makefile index 1a97c59..e8a53e3 100644 --- a/Makefile +++ b/Makefile @@ -9,8 +9,7 @@ ## ## Images (dev builds, tagged from git describe): ## make dev-sandbox # build sandbox image (native arch) -## make dev-runner # build runner image -## make dev-push # build + push both (sandbox multi-arch) +## make dev-push # build + push sandbox image (multi-arch) ## Release images are built and pushed by .github/workflows/images.yml. REGISTRY ?= ghcr.io/robbycochran/harness-openshell @@ -19,7 +18,7 @@ PLATFORM := linux/amd64 VERSION := $(shell git describe --tags --always 2>/dev/null || echo dev) LDFLAGS := -s -w -X main.version=$(VERSION) -DEV_SANDBOX_IMAGE := $(REGISTRY):sandbox-$(VERSION) +IMAGE := $(REGISTRY):sandbox-$(VERSION) .PHONY: all cli \ vet lint test test-local test-kind test-remote test-all \ @@ -66,15 +65,15 @@ test-local: cli ## Builds sandbox image locally and pre-loads into kind (no registry push needed). ## Use KEEP=1 to keep the cluster after tests (for debugging). test-kind: cli - $(CONTAINER_CLI) build -t $(DEV_SANDBOX_IMAGE) sandbox/ + $(CONTAINER_CLI) build -t $(IMAGE) sandbox/ @echo "" - SANDBOX_IMAGE=$(DEV_SANDBOX_IMAGE) CONTAINER_CLI=$(CONTAINER_CLI) ./test/kind-lifecycle.sh $(if $(KEEP),--keep) + HARNESS_OS_IMAGE=$(IMAGE) CONTAINER_CLI=$(CONTAINER_CLI) ./test/kind-lifecycle.sh $(if $(KEEP),--keep) ## Remote (OCP): requires KUBECONFIG set test-remote: cli dev-sandbox @test -n "$${KUBECONFIG}" || { echo "ERROR: Set KUBECONFIG for OCP (e.g. export KUBECONFIG=infracluster/kubeconfig)"; exit 1; } @echo "" - SANDBOX_IMAGE=$(DEV_SANDBOX_IMAGE) ./test/test-flow.sh ocp + HARNESS_OS_IMAGE=$(IMAGE) ./test/test-flow.sh ocp ## All: unit + local + kind + remote test-all: test test-local test-kind test-remote @@ -83,17 +82,17 @@ test-all: test test-local test-kind test-remote ## Build dev sandbox image locally (native arch only) dev-sandbox: - $(CONTAINER_CLI) build -t $(DEV_SANDBOX_IMAGE) sandbox/ - @echo "Built: $(DEV_SANDBOX_IMAGE)" + $(CONTAINER_CLI) build -t $(IMAGE) sandbox/ + @echo "Built: $(IMAGE)" ## Build and push dev sandbox image (multi-arch) dev-push: - @$(CONTAINER_CLI) rmi --force $(DEV_SANDBOX_IMAGE) 2>/dev/null || true - @$(CONTAINER_CLI) manifest rm $(DEV_SANDBOX_IMAGE) 2>/dev/null || true - $(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) - @echo "Pushed: $(DEV_SANDBOX_IMAGE) (multi-arch)" + @$(CONTAINER_CLI) rmi --force $(IMAGE) 2>/dev/null || true + @$(CONTAINER_CLI) manifest rm $(IMAGE) 2>/dev/null || true + $(CONTAINER_CLI) build --platform linux/amd64 --manifest $(IMAGE) sandbox/ + $(CONTAINER_CLI) build --platform linux/arm64 --manifest $(IMAGE) sandbox/ + $(CONTAINER_CLI) manifest push $(IMAGE) + @echo "Pushed: $(IMAGE) (multi-arch)" ## ── Convenience targets ─────────────────────────────────────────────── diff --git a/SPEC.md b/SPEC.md index 8267b3c..2667567 100644 --- a/SPEC.md +++ b/SPEC.md @@ -37,7 +37,7 @@ env: Fields: - `name` (required) -- sandbox name, used for `openshell sandbox connect` -- `image` -- container image for the sandbox (default: version-matched from ghcr.io, override with `SANDBOX_IMAGE` env) +- `image` -- container image for the sandbox (default: version-matched from ghcr.io, override with `HARNESS_OS_IMAGE` env) - `entrypoint` -- command to run (default: `claude`). Supports `claude`, `opencode`, `bash`, or any binary on PATH. - `tty` -- enable TTY (default: false) - `task` -- path to a task.md file, passed to entrypoint via `-p "$(cat task.md)"` @@ -115,23 +115,25 @@ The CLI resolves images from its embedded version (set via `-ldflags` at build t - `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` env var overrides the version-based resolution. +`HARNESS_OS_IMAGE` env var overrides the version-based resolution. ## Environment Variables +Harness-specific variables use the `HARNESS_OS_` prefix. OpenShell runtime variables use `OPENSHELL_`. + | Variable | Purpose | |----------|---------| -| `SANDBOX_IMAGE` | Override sandbox image (dev/CI builds) | -| `HARNESS_DIR` | Override harness directory detection | -| `OPENSHELL_NAMESPACE` | Override K8s namespace (default: `openshell`) | +| `HARNESS_OS_DIR` | Override harness directory detection | +| `HARNESS_OS_IMAGE` | Override sandbox image (dev/CI builds) | +| `HARNESS_OS_GATEWAY` | Override gateway name in gateway config | +| `HARNESS_OS_PULL_SECRET` | Image pull secret name passed to Helm install | +| `HARNESS_OS_SANDBOX_PULL_SECRET` | Sandbox image pull secret name passed to Helm install | | `OPENSHELL_CLI` | Override openshell binary path | +| `OPENSHELL_NAMESPACE` | Override K8s namespace (default: `openshell`) | | `OPENSHELL_MODEL` | Inference model for provider registration (default: `claude-sonnet-4-6`) | | `OPENSHELL_CHART_VERSION` | Override Helm chart version (beats `gateway.yaml`) | -| `PULL_SECRET` / `SANDBOX_PULL_SECRET` | Image pull secret names passed to the Helm install | | `KUBECONFIG` | K8s cluster config for remote targets | -`GATEWAY_NAME` is internal -- used by env override in gateway config, not typically set by users. - ## Payload The harness renders agent config into a self-contained payload uploaded to `/sandbox/.config/openshell/`: diff --git a/TODO.md b/TODO.md index 28dc104..5f1756b 100644 --- a/TODO.md +++ b/TODO.md @@ -10,7 +10,7 @@ - Prerequisite: proto files stabilize (OpenShell is alpha) ### Image registry as gateway config vs env override -- `SANDBOX_IMAGE` env var overrides the version-based image resolution (for dev/CI) +- `HARNESS_OS_IMAGE` env var overrides the version-based image resolution (for dev/CI) - Consider: gateway.yaml uses a `registry` field and images are relative to it ### registerProviders should filter by agent's provider list diff --git a/cmd/deploy.go b/cmd/deploy.go index 7f03b9a..fae030b 100644 --- a/cmd/deploy.go +++ b/cmd/deploy.go @@ -226,16 +226,16 @@ func deployFromConfig(harnessDir string, gwCfg *gateway.GatewayConfig, gw gatewa } else if valuesPath != "" { helmArgs = append(helmArgs, "--values", valuesPath) } - if sandboxImage := os.Getenv("SANDBOX_IMAGE"); sandboxImage != "" { + if sandboxImage := os.Getenv("HARNESS_OS_IMAGE"); sandboxImage != "" { helmArgs = append(helmArgs, "--set", "server.sandboxImage="+sandboxImage) } if routeHost != "" { helmArgs = append(helmArgs, "--set", "pkiInitJob.serverDnsNames[0]="+routeHost) } - if ps := os.Getenv("PULL_SECRET"); ps != "" { + if ps := os.Getenv("HARNESS_OS_PULL_SECRET"); ps != "" { helmArgs = append(helmArgs, "--set", "imagePullSecrets[0].name="+ps) } - if sps := os.Getenv("SANDBOX_PULL_SECRET"); sps != "" { + if sps := os.Getenv("HARNESS_OS_SANDBOX_PULL_SECRET"); sps != "" { helmArgs = append(helmArgs, "--set", "server.sandboxImagePullSecrets[0].name="+sps) } if err := kc.RunHelm(ctx, helmArgs...); err != nil { diff --git a/cmd/sandbox_image.go b/cmd/sandbox_image.go index 90a315e..b6dfe3a 100644 --- a/cmd/sandbox_image.go +++ b/cmd/sandbox_image.go @@ -2,10 +2,8 @@ package cmd import "os" -// resolveSandboxImage returns the final sandbox image path following -// the precedence: SANDBOX_IMAGE env var > agentImage > version-derived default. func resolveSandboxImage(agentImage string) string { - if envImage := os.Getenv("SANDBOX_IMAGE"); envImage != "" { + if envImage := os.Getenv("HARNESS_OS_IMAGE"); envImage != "" { return envImage } if agentImage != "" { diff --git a/cmd/up_test.go b/cmd/up_test.go index 1ed16dc..12ec0b9 100644 --- a/cmd/up_test.go +++ b/cmd/up_test.go @@ -167,7 +167,7 @@ func TestActiveGatewayInfo_LocalGateway(t *testing.T) { } func TestUpLocal_SandboxCreateOpts(t *testing.T) { - t.Setenv("SANDBOX_IMAGE", "") + t.Setenv("HARNESS_OS_IMAGE", "") dir := setupTestAgent(t) gw := &mockGW{ providerList: []string{"github", "vertex-local"}, diff --git a/dev-harness.sh b/dev-harness.sh index 2d3ca51..e784929 100755 --- a/dev-harness.sh +++ b/dev-harness.sh @@ -26,5 +26,5 @@ else make -C "$REPO_ROOT" cli dev-sandbox fi -export SANDBOX_IMAGE="${REGISTRY}:sandbox-${VERSION}" +export HARNESS_OS_IMAGE="${REGISTRY}:sandbox-${VERSION}" exec "$REPO_ROOT/harness" "$@" diff --git a/internal/gateway/config.go b/internal/gateway/config.go index fac18bf..7fae923 100644 --- a/internal/gateway/config.go +++ b/internal/gateway/config.go @@ -156,7 +156,7 @@ func (c *GatewayConfig) applyDefaults() { } func (c *GatewayConfig) applyEnvOverrides() { - if v := os.Getenv("GATEWAY_NAME"); v != "" { + if v := os.Getenv("HARNESS_OS_GATEWAY"); v != "" { c.Gateway.Name = v } } diff --git a/internal/gateway/config_test.go b/internal/gateway/config_test.go index cd70e7a..99505f2 100644 --- a/internal/gateway/config_test.go +++ b/internal/gateway/config_test.go @@ -167,7 +167,7 @@ gateway: name: original-name `) - t.Setenv("GATEWAY_NAME", "env-gw-name") + t.Setenv("HARNESS_OS_GATEWAY", "env-gw-name") cfg, err := LoadConfig(dir) if err != nil { @@ -175,7 +175,7 @@ gateway: } if cfg.Gateway.Name != "env-gw-name" { - t.Errorf("GATEWAY_NAME override: got %q", cfg.Gateway.Name) + t.Errorf("HARNESS_OS_GATEWAY override: got %q", cfg.Gateway.Name) } } @@ -187,7 +187,7 @@ gateway: name: original-name `) - t.Setenv("GATEWAY_NAME", "") + t.Setenv("HARNESS_OS_GATEWAY", "") cfg, err := LoadConfig(dir) if err != nil { diff --git a/main.go b/main.go index e6b7675..72bd548 100644 --- a/main.go +++ b/main.go @@ -77,7 +77,7 @@ func main() { } func detectHarnessDir() string { - if d := os.Getenv("HARNESS_DIR"); d != "" { + if d := os.Getenv("HARNESS_OS_DIR"); d != "" { return d } var roots []string diff --git a/test/kind-lifecycle.sh b/test/kind-lifecycle.sh index da96995..7d9a883 100755 --- a/test/kind-lifecycle.sh +++ b/test/kind-lifecycle.sh @@ -76,22 +76,22 @@ kubectl create namespace openshell --dry-run=client -o yaml | kubectl apply -f - # local openshell service. Locally, preload from the container daemon so # tests work without a registry push. CONTAINER_CLI=${CONTAINER_CLI:-podman} -if [[ -n "${SANDBOX_IMAGE:-}" ]] && [[ -z "${CI:-}" ]]; then - if "$CONTAINER_CLI" image inspect "$SANDBOX_IMAGE" &>/dev/null; then - echo " Pre-loading image: $SANDBOX_IMAGE" +if [[ -n "${HARNESS_OS_IMAGE:-}" ]] && [[ -z "${CI:-}" ]]; then + if "$CONTAINER_CLI" image inspect "$HARNESS_OS_IMAGE" &>/dev/null; then + echo " Pre-loading image: $HARNESS_OS_IMAGE" if [[ "$CONTAINER_CLI" == "docker" ]]; then - kind load docker-image "$SANDBOX_IMAGE" --name "$CLUSTER_NAME" + kind load docker-image "$HARNESS_OS_IMAGE" --name "$CLUSTER_NAME" else # kind only loads from the docker daemon directly; podman goes via archive IMAGE_ARCHIVE=$(mktemp /tmp/kind-sandbox-image-XXXXXX.tar) - "$CONTAINER_CLI" save "$SANDBOX_IMAGE" -o "$IMAGE_ARCHIVE" + "$CONTAINER_CLI" save "$HARNESS_OS_IMAGE" -o "$IMAGE_ARCHIVE" kind load image-archive "$IMAGE_ARCHIVE" --name "$CLUSTER_NAME" rm -f "$IMAGE_ARCHIVE" fi else echo " Image not found locally — kind will pull from registry at sandbox create time" fi -elif [[ -n "${SANDBOX_IMAGE:-}" ]]; then +elif [[ -n "${HARNESS_OS_IMAGE:-}" ]]; then echo " CI mode — kind will pull image from registry at sandbox create time" fi From 19cf29c836624756a7df7164ab3be6e0cbc8d2ec Mon Sep 17 00:00:00 2001 From: Robby Cochran Date: Fri, 12 Jun 2026 20:51:00 -0700 Subject: [PATCH 5/6] ci: strip ANSI codes from test log artifacts Pipe output through sed to remove ANSI escape sequences before writing to the log file. The log artifacts are now clean ASCII text. --- test/test-flow.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test-flow.sh b/test/test-flow.sh index 01cb0f5..c6de262 100755 --- a/test/test-flow.sh +++ b/test/test-flow.sh @@ -60,7 +60,7 @@ fi LOG_FILE="${TEST_LOG_FILE:-}" if [[ -n "$LOG_FILE" ]]; then mkdir -p "$(dirname "$LOG_FILE")" - exec > >(tee -a "$LOG_FILE") 2>&1 + exec > >(sed -u 's/\x1b\[[0-9;]*m//g' | tee -a "$LOG_FILE") 2>&1 fi harness() { From 367b8c30ae717f58b793bd8a02404fc2e7588e50 Mon Sep 17 00:00:00 2001 From: Robby Cochran Date: Fri, 12 Jun 2026 21:29:06 -0700 Subject: [PATCH 6/6] fix: preload sandbox image into kind on CI The kind-lifecycle script was skipping image preload when CI=true, forcing the kind cluster to pull from the registry at sandbox create time. This caused slow or stuck pulls for the ~800MB sandbox image. Now the locally-built image is always preloaded into kind via 'kind load docker-image' when available, regardless of CI mode. --- test/kind-lifecycle.sh | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/test/kind-lifecycle.sh b/test/kind-lifecycle.sh index 7d9a883..41186f4 100755 --- a/test/kind-lifecycle.sh +++ b/test/kind-lifecycle.sh @@ -72,17 +72,15 @@ kubectl create namespace openshell --dry-run=client -o yaml | kubectl apply -f - # Pre-load dev sandbox image into kind. # In CI (CI=true), images.yml has already pushed the image to the registry so -# kind can pull it directly — skip the 60-90s local preload that disrupts the -# local openshell service. Locally, preload from the container daemon so -# tests work without a registry push. +# Pre-load the sandbox image into kind from the local container daemon +# so tests work without a registry push. CONTAINER_CLI=${CONTAINER_CLI:-podman} -if [[ -n "${HARNESS_OS_IMAGE:-}" ]] && [[ -z "${CI:-}" ]]; then +if [[ -n "${HARNESS_OS_IMAGE:-}" ]]; then if "$CONTAINER_CLI" image inspect "$HARNESS_OS_IMAGE" &>/dev/null; then echo " Pre-loading image: $HARNESS_OS_IMAGE" if [[ "$CONTAINER_CLI" == "docker" ]]; then kind load docker-image "$HARNESS_OS_IMAGE" --name "$CLUSTER_NAME" else - # kind only loads from the docker daemon directly; podman goes via archive IMAGE_ARCHIVE=$(mktemp /tmp/kind-sandbox-image-XXXXXX.tar) "$CONTAINER_CLI" save "$HARNESS_OS_IMAGE" -o "$IMAGE_ARCHIVE" kind load image-archive "$IMAGE_ARCHIVE" --name "$CLUSTER_NAME" @@ -91,8 +89,6 @@ if [[ -n "${HARNESS_OS_IMAGE:-}" ]] && [[ -z "${CI:-}" ]]; then else echo " Image not found locally — kind will pull from registry at sandbox create time" fi -elif [[ -n "${HARNESS_OS_IMAGE:-}" ]]; then - echo " CI mode — kind will pull image from registry at sandbox create time" fi # ── Run tests ───────────────────────────────────────────────────────