Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
253 changes: 222 additions & 31 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,44 @@ on:
tags: ["v*"]

concurrency:
group: release
group: release-${{ github.ref }}
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Per-ref concurrency allows tag release workflows to run in parallel, but the workflow still updates shared latest tags. Two tag releases close together can race and overwrite latest nondeterministically. Consider restoring a shared concurrency group for tag releases or otherwise serializing push-latest.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/release.yaml, line 9:

<comment>Per-ref concurrency allows tag release workflows to run in parallel, but the workflow still updates shared `latest` tags. Two tag releases close together can race and overwrite `latest` nondeterministically. Consider restoring a shared concurrency group for tag releases or otherwise serializing `push-latest`.</comment>

<file context>
@@ -6,23 +6,44 @@ on:
 
 concurrency:
-  group: release
+  group: release-${{ github.ref }}
   cancel-in-progress: false
 
</file context>
Suggested change
group: release-${{ github.ref }}
group: release
Fix with Cubic

cancel-in-progress: false

jobs:
release:
prepare:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- name: Determine version
id: version
run: |
if [[ "${GITHUB_REF}" == refs/tags/v* ]]; then
echo "version=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT"
else
echo "version=main" >> "$GITHUB_OUTPUT"
fi

agent-base:
needs: prepare
permissions:
contents: write
contents: read
packages: write
strategy:
fail-fast: false
matrix:
arch: [amd64, arm64]
include:
- arch: amd64
runner: ubuntu-latest
- arch: arm64
runner: ubuntu-24.04-arm
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Login to GHCR
uses: docker/login-action@v3
Expand All @@ -31,49 +52,219 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Determine version
id: version
- name: Build and push agent-base
env:
BUILDX_CACHE_TYPE: gha
VERSION: ${{ needs.prepare.outputs.version }}-${{ matrix.arch }}
run: |
if [[ "${GITHUB_REF}" == refs/tags/v* ]]; then
echo "version=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT"
else
echo "version=main" >> "$GITHUB_OUTPUT"
fi
docker buildx build \
--cache-from type=$BUILDX_CACHE_TYPE,scope=agent-base-${{ matrix.arch }} \
--cache-to type=$BUILDX_CACHE_TYPE,scope=agent-base-${{ matrix.arch }},mode=max \
--provenance=false \
-t ghcr.io/kelos-dev/agent-base:$VERSION \
-f agent-base/Dockerfile --push .

- name: Build images
go-images:
needs: prepare
permissions:
contents: read
packages: write
strategy:
fail-fast: false
matrix:
dir:
- cmd/kelos-controller
- cmd/kelos-spawner
- cmd/kelos-token-refresher
- cmd/ghproxy
arch: [amd64, arm64]
include:
- arch: amd64
runner: ubuntu-latest
- arch: arm64
runner: ubuntu-24.04-arm
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and push image
env:
VERSION: ${{ steps.version.outputs.version }}
run: make image VERSION="$VERSION"
BUILDX_CACHE_TYPE: gha
VERSION: ${{ needs.prepare.outputs.version }}-${{ matrix.arch }}
run: |
name=$(basename ${{ matrix.dir }})
docker buildx build \
--cache-from type=$BUILDX_CACHE_TYPE,scope=$name-${{ matrix.arch }} \
--cache-to type=$BUILDX_CACHE_TYPE,scope=$name-${{ matrix.arch }},mode=max \
--provenance=false \
-t ghcr.io/kelos-dev/$name:$VERSION \
-f ${{ matrix.dir }}/Dockerfile --push .

agent-images:
needs: [prepare, agent-base]
permissions:
contents: read
packages: write
strategy:
fail-fast: false
matrix:
dir:
- claude-code
- codex
- gemini
- opencode
- cursor
arch: [amd64, arm64]
include:
- arch: amd64
runner: ubuntu-latest
- arch: arm64
runner: ubuntu-24.04-arm
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v4

- name: Push images
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and push image
env:
VERSION: ${{ steps.version.outputs.version }}
run: make push VERSION="$VERSION"
BUILDX_CACHE_TYPE: gha
VERSION: ${{ needs.prepare.outputs.version }}-${{ matrix.arch }}
BASE_IMAGE: ghcr.io/kelos-dev/agent-base:${{ needs.prepare.outputs.version }}-${{ matrix.arch }}
run: |
name=$(basename ${{ matrix.dir }})
docker buildx build \
--cache-from type=$BUILDX_CACHE_TYPE,scope=$name-${{ matrix.arch }} \
--cache-to type=$BUILDX_CACHE_TYPE,scope=$name-${{ matrix.arch }},mode=max \
--build-arg BASE_IMAGE=$BASE_IMAGE \
--provenance=false \
-t ghcr.io/kelos-dev/$name:$VERSION \
-f ${{ matrix.dir }}/Dockerfile --push .

manifests:
needs: [prepare, agent-base, go-images, agent-images]
permissions:
contents: read
packages: write
strategy:
fail-fast: false
matrix:
image:
- agent-base
- kelos-controller
- kelos-spawner
- kelos-token-refresher
- ghproxy
- claude-code
- codex
- gemini
- opencode
- cursor
runs-on: ubuntu-latest
steps:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Create multi-arch manifest
run: |
docker buildx imagetools create \
-t ghcr.io/kelos-dev/${{ matrix.image }}:${{ needs.prepare.outputs.version }} \
ghcr.io/kelos-dev/${{ matrix.image }}:${{ needs.prepare.outputs.version }}-amd64 \
ghcr.io/kelos-dev/${{ matrix.image }}:${{ needs.prepare.outputs.version }}-arm64

push-latest:
if: startsWith(github.ref, 'refs/tags/v')
needs: [prepare, manifests]
permissions:
contents: read
packages: write
strategy:
fail-fast: false
matrix:
image:
- agent-base
- kelos-controller
- kelos-spawner
- kelos-token-refresher
- ghproxy
- claude-code
- codex
- gemini
- opencode
- cursor
runs-on: ubuntu-latest
steps:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Push latest tags for releases
if: startsWith(github.ref, 'refs/tags/v')
- name: Tag as latest
run: |
make image VERSION=latest
make push VERSION=latest
docker buildx imagetools create \
-t ghcr.io/kelos-dev/${{ matrix.image }}:latest \
ghcr.io/kelos-dev/${{ matrix.image }}:${{ needs.prepare.outputs.version }}

release-binaries:
if: startsWith(github.ref, 'refs/tags/v')
needs: prepare
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- uses: actions/setup-go@v5
with:
go-version-file: go.mod

- name: Build CLI binaries
if: startsWith(github.ref, 'refs/tags/v')
env:
VERSION: ${{ steps.version.outputs.version }}
VERSION: ${{ needs.prepare.outputs.version }}
run: make release-binaries VERSION="$VERSION"

- name: Generate release notes
if: startsWith(github.ref, 'refs/tags/v')
env:
GH_TOKEN: ${{ github.token }}
VERSION: ${{ steps.version.outputs.version }}
VERSION: ${{ needs.prepare.outputs.version }}
run: go run ./hack/release-notes "$VERSION" > /tmp/release-notes.md

- name: Upload CLI binaries to GitHub release
if: startsWith(github.ref, 'refs/tags/v')
env:
GH_TOKEN: ${{ github.token }}
VERSION: ${{ steps.version.outputs.version }}
VERSION: ${{ needs.prepare.outputs.version }}
run: |
gh release create "$VERSION" --verify-tag --draft --title "$VERSION" --notes-file /tmp/release-notes.md || true
gh release upload "$VERSION" --clobber \
Expand Down
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Image configuration
REGISTRY ?= ghcr.io/kelos-dev
VERSION ?= latest
IMAGE_DIRS ?= cmd/kelos-controller cmd/kelos-spawner cmd/kelos-token-refresher cmd/ghproxy claude-code codex gemini opencode cursor
IMAGE_DIRS ?= agent-base cmd/kelos-controller cmd/kelos-spawner cmd/kelos-token-refresher cmd/ghproxy claude-code codex gemini opencode cursor

# Version injection for the kelos CLI – only set ldflags when an explicit
# version is given so that dev builds fall through to runtime/debug info.
Expand Down Expand Up @@ -95,6 +95,7 @@ push: ## Push docker images (use WHAT to push specific image).
docker push $(REGISTRY)/$$(basename $$dir):$(VERSION); \
done


RELEASE_PLATFORMS ?= linux/amd64 linux/arm64 darwin/amd64 darwin/arm64

.PHONY: release-binaries
Expand Down
10 changes: 9 additions & 1 deletion cmd/ghproxy/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
FROM golang:1.25 AS builder
WORKDIR /workspace
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN --mount=type=cache,target=/root/.cache/go-build \
CGO_ENABLED=0 go build -o bin/ghproxy ./cmd/ghproxy

FROM gcr.io/distroless/static:nonroot
WORKDIR /
COPY bin/ghproxy .
COPY --from=builder /workspace/bin/ghproxy .
USER 65532:65532
ENTRYPOINT ["/ghproxy"]
10 changes: 9 additions & 1 deletion cmd/kelos-spawner/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
FROM golang:1.25 AS builder
WORKDIR /workspace
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN --mount=type=cache,target=/root/.cache/go-build \
CGO_ENABLED=0 go build -o bin/kelos-spawner ./cmd/kelos-spawner

FROM gcr.io/distroless/static:nonroot
WORKDIR /
COPY bin/kelos-spawner .
COPY --from=builder /workspace/bin/kelos-spawner .
USER 65532:65532
ENTRYPOINT ["/kelos-spawner"]
32 changes: 2 additions & 30 deletions codex/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,40 +1,12 @@
FROM ubuntu:24.04

ARG GO_VERSION=1.25.0

RUN apt-get update && apt-get install -y \
make \
curl \
ca-certificates \
git \
&& curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
&& apt-get install -y nodejs \
&& curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
-o /usr/share/keyrings/githubcli-archive-keyring.gpg \
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
> /etc/apt/sources.list.d/github-cli.list \
&& apt-get update \
&& apt-get install -y gh \
&& rm -rf /var/lib/apt/lists/*

RUN ARCH=$(dpkg --print-architecture) \
&& TARBALL="go${GO_VERSION}.linux-${ARCH}.tar.gz" \
&& curl -fsSL -o "/tmp/${TARBALL}" "https://dl.google.com/go/${TARBALL}" \
&& curl -fsSL -o "/tmp/${TARBALL}.sha256" "https://dl.google.com/go/${TARBALL}.sha256" \
&& echo "$(cat /tmp/${TARBALL}.sha256) /tmp/${TARBALL}" | sha256sum -c - \
&& tar -C /usr/local -xzf "/tmp/${TARBALL}" \
&& rm "/tmp/${TARBALL}" "/tmp/${TARBALL}.sha256"

ENV PATH="/usr/local/go/bin:${PATH}"
ARG BASE_IMAGE=agent-base:latest
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Base image uses mutable latest tag, making builds non-reproducible and allowing upstream changes without review.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At codex/Dockerfile, line 1:

<comment>Base image uses mutable `latest` tag, making builds non-reproducible and allowing upstream changes without review.</comment>

<file context>
@@ -1,40 +1,12 @@
-    && rm "/tmp/${TARBALL}" "/tmp/${TARBALL}.sha256"
-
-ENV PATH="/usr/local/go/bin:${PATH}"
+ARG BASE_IMAGE=agent-base:latest
+FROM ${BASE_IMAGE}
 
</file context>
Fix with Cubic

FROM ${BASE_IMAGE}
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Removal of the kelos-capture binary copy likely breaks the entrypoint contract: /kelos/kelos-capture is still invoked by the entrypoint and documented as required, but this Dockerfile no longer ensures it exists in the image.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At codex/Dockerfile, line 2:

<comment>Removal of the `kelos-capture` binary copy likely breaks the entrypoint contract: `/kelos/kelos-capture` is still invoked by the entrypoint and documented as required, but this Dockerfile no longer ensures it exists in the image.</comment>

<file context>
@@ -1,40 +1,12 @@
-
-ENV PATH="/usr/local/go/bin:${PATH}"
+ARG BASE_IMAGE=agent-base:latest
+FROM ${BASE_IMAGE}
 
 ARG CODEX_VERSION=0.117.0
</file context>
Fix with Cubic


ARG CODEX_VERSION=0.117.0
RUN npm install -g @openai/codex@${CODEX_VERSION}

COPY codex/kelos_entrypoint.sh /kelos_entrypoint.sh
RUN chmod +x /kelos_entrypoint.sh

COPY bin/kelos-capture /kelos/kelos-capture

RUN useradd -u 61100 -m -s /bin/bash agent
RUN mkdir -p /home/agent/.codex && chown -R agent:agent /home/agent

Expand Down
Loading
Loading