Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"plugins": [
{
"name": "genie",
"version": "5.260726.7",
"version": "5.260726.9",
"source": "./plugins/genie",
"description": "Human-AI partnership for Claude Code. Share a terminal, orchestrate workers, evolve together. Brainstorm ideas, wish them into plans, make with parallel agents, ship as one team. A coding genie that grows with your project."
}
Expand Down
105 changes: 105 additions & 0 deletions .github/workflows/musl-adapter-smoke.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
name: Musl Adapter Smoke

# The Alpine execution adapter historically ran ONLY inside production release
# dogfood jobs, so every adapter defect cost a full release cycle to discover
# (docker pull noise on the stderr-strict probe, container-root vs host-uid git
# ownership). This smoke exercises the adapter on a real amd64 runner with the
# real previous-stable musl binary on every PR that touches it: seeding a git
# repository through the container exactly as the dogfood harness does, and
# asserting the probe's contract — exit 0 with a byte-empty stderr.
on:
pull_request:
paths:
- 'scripts/run-musl-dogfood.sh'
- '.github/workflows/musl-adapter-smoke.yml'

permissions:
contents: read

jobs:
smoke:
name: Seed a repository through the Alpine adapter
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
with:
persist-credentials: false

- name: Pre-pull the pinned Alpine image (mirrors the dogfood job)
run: docker pull -q 'alpine:3.19@sha256:6baf43584bcb78f2e5847d1de515f23499913ac9f12bdf834811a3145eb11ca1'

- name: Download the previous-stable musl release binary
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
PREVIOUS_TAG="v$(jq -r '.version' .well-known/latest.json 2>/dev/null || true)"
if [[ "$PREVIOUS_TAG" == "v" || "$PREVIOUS_TAG" == "vnull" ]]; then
echo "::error::could not resolve the previous stable version from .well-known/latest.json"
exit 1
fi
gh release download "$PREVIOUS_TAG" --repo "$GITHUB_REPOSITORY" \
--pattern "genie-*-linux-x64-musl.tar.gz" --dir "$RUNNER_TEMP/previous"

- name: Probe-mode contract — exit 0 with a byte-empty stderr
run: |
set -euo pipefail
ROOT="${RUNNER_TEMP}/dogfood-root"
mkdir -p "$ROOT/repos/a" "$ROOT/home" "$ROOT/genie-home/bin" "$ROOT/tmp" "$ROOT/n-extract"
tar -xzf "$RUNNER_TEMP"/previous/genie-*-linux-x64-musl.tar.gz -C "$ROOT/n-extract"
cp "$ROOT/n-extract/genie" "$ROOT/genie-home/bin/genie"
chmod 755 "$ROOT/genie-home/bin/genie"
cp "$ROOT/n-extract/VERSION" "$ROOT/genie-home/bin/VERSION" || true
# The harness's verifyCapability runs the adapter WITHOUT DOGFOOD_ROOT
# (candidate mounted read-only at /candidate) and rejects any stderr
# byte. --version is quiet on every generation, so bootstrap noise
# (docker pull progress, apk output) is the only thing that could
# reach stderr — exactly the class this contract pins.
status=0
HOME="$ROOT/home" TMPDIR="$ROOT/tmp" TERM=xterm \
bash scripts/run-musl-dogfood.sh "$ROOT/genie-home/bin/genie" --version \
>"$RUNNER_TEMP/probe-out" 2>"$RUNNER_TEMP/probe-err" || status=$?
if [[ "$status" -ne 0 ]]; then
echo "::error::probe-mode adapter invocation exited ${status}"
cat "$RUNNER_TEMP/probe-out" "$RUNNER_TEMP/probe-err"
exit 1
fi
if [[ -s "$RUNNER_TEMP/probe-err" ]]; then
echo "::error::probe-mode adapter invocation wrote stderr (the capability probe fails on any stderr byte):"
cat "$RUNNER_TEMP/probe-err"
exit 1
fi
grep -q . "$RUNNER_TEMP/probe-out" || { echo "::error::--version produced no output"; exit 1; }

- name: Stateful-mode seeding — the exact leg that failed in v5.260726.9
run: |
set -euo pipefail
ROOT="${RUNNER_TEMP}/dogfood-root"
git init -q -b main "$ROOT/repos/a"
ADAPTER="$PWD/scripts/run-musl-dogfood.sh"
cd "$ROOT/repos/a"
run_via_adapter() {
local status=0
DOGFOOD_ROOT="$ROOT" HOME="$ROOT/home" GENIE_HOME="$ROOT/genie-home" \
TMPDIR="$ROOT/tmp" GENIE_RELEASE_DOGFOOD=1 TERM=xterm \
bash "$ADAPTER" "$ROOT/genie-home/bin/genie" "$@" \
>"$RUNNER_TEMP/cli-out" 2>"$RUNNER_TEMP/cli-err" || status=$?
if [[ "$status" -ne 0 ]]; then
echo "::error::adapter invocation 'genie $*' exited ${status}"
cat "$RUNNER_TEMP/cli-out" "$RUNNER_TEMP/cli-err"
exit 1
fi
cat "$RUNNER_TEMP/cli-out"
}
run_via_adapter init
run_via_adapter task create --title smoke-sentinel --wish smoke
run_via_adapter board
# The database seeded through the container must be owned by the
# runner user, or every later host-side stage fails closed on
# genie's stat.uid ownership checks.
[[ "$(stat -c %u "$ROOT/repos/a/.genie/genie.db")" == "$(id -u)" ]] || {
echo "::error::container-created database is not owned by the host user"
exit 1
}
echo "adapter smoke: OK"
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@automagik/genie",

"version": "5.260726.7",
"version": "5.260726.9",
"description": "Collaborative terminal toolkit for human + AI workflows. NOTE: npm distribution discontinued 2026-05-09 — install via `curl -fsSL https://raw.githubusercontent.com/automagik-dev/genie/main/install.sh | bash` (cosign + SLSA verified). See https://automagik.dev/genie/release-process",

"license": "MIT",
Expand Down
2 changes: 1 addition & 1 deletion plugins/genie/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "genie",
"version": "5.260726.7",
"version": "5.260726.9",
"description": "Human-AI partnership for Claude Code. Share a terminal, orchestrate workers, evolve together. Brainstorm ideas, turn them into wishes, execute with /work, validate with /review, and ship as one team.",
"author": {
"name": "Namastex Labs"
Expand Down
2 changes: 1 addition & 1 deletion plugins/genie/.codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "genie",
"version": "5.260726.7",
"version": "5.260726.9",
"description": "Plan, execute, review, and ship software with Genie workflows in Codex.",
"author": {
"name": "Namastex Labs",
Expand Down
2 changes: 1 addition & 1 deletion plugins/genie/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "genie-plugin",
"version": "5.260726.7",
"version": "5.260726.9",
"private": true,
"description": "Runtime dependencies for genie bundled CLIs",
"license": "MIT",
Expand Down
2 changes: 1 addition & 1 deletion plugins/hermes-genie/plugin.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: genie
version: 5.260726.7
version: 5.260726.9
description: "Native Hermes surface for Genie orchestration: read-only status plus work-plan and review-plan gap tools that the MCP board surface does not cover, hooks, commands, and a thin cockpit skill. Board/task truth comes from the genie MCP tools."
provides_tools:
# Default surface: exactly the three gap tools the MCP board surface does not cover.
Expand Down
15 changes: 13 additions & 2 deletions scripts/run-musl-dogfood.sh
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,15 @@ fi
# write pull progress to stderr, which the harness's stderr-strict capability
# probe treats as failure. The dogfood workflow pre-pulls the digest.
docker_args=(run --rm -i --security-opt no-new-privileges)

# The candidate must run AS THE HOST USER, not container root. The fixture tree
# is bind-mounted and shared with host-side stages: git refuses a repository
# whose on-disk owner differs from the effective uid ("dubious ownership", so
# `genie init` reports "not a git repository"), genie's own fail-closed
# ownership checks demand stat.uid == process uid, and any root-owned file the
# container creates would poison every later host-side stage. Root is needed
# only for the apk bootstrap; su-exec then drops to this exact identity.
docker_args+=(--env "DOGFOOD_HOST_UID=$(id -u)" --env "DOGFOOD_HOST_GID=$(id -g)")
container_candidate="/candidate/${candidate_name}"

# The one-shot capability probe needs only a read-only candidate directory.
Expand Down Expand Up @@ -98,8 +107,10 @@ exec docker "${docker_args[@]}" \
# Bootstrap noise must never reach stderr: the probe fails on any stderr
# byte, and only the candidate binary output is meaningful to it. apk
# failures still abort through set -e.
apk add --no-cache bash git libstdc++ >/dev/null 2>&1
apk add --no-cache bash git libstdc++ su-exec >/dev/null 2>&1
candidate=$1
shift
exec "$candidate" "$@"
# Drop from bootstrap root to the exact host identity that owns the
# bind-mounted fixture tree (see DOGFOOD_HOST_UID rationale above).
exec su-exec "${DOGFOOD_HOST_UID}:${DOGFOOD_HOST_GID}" "$candidate" "$@"
' sh "$container_candidate" "$@"
7 changes: 7 additions & 0 deletions scripts/run-musl-dogfood.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ describe('musl dogfood execution adapter', () => {
// the manifest every run and write pull progress to stderr, failing the
// harness's stderr-strict capability probe.
expect(args).not.toContain('--pull=always');
// The candidate must execute as the host identity, not container root:
// the bind-mounted fixture tree is shared with host-side stages, and both
// git (dubious ownership) and genie's own ownership checks fail-close on a
// uid mismatch. Root exists only for the apk bootstrap; su-exec drops it.
expect(args).toContain(`DOGFOOD_HOST_UID=${process.getuid?.()}`);
expect(args).toContain(`DOGFOOD_HOST_GID=${process.getgid?.()}`);
expect(args.some((line) => line.includes('su-exec "${DOGFOOD_HOST_UID}:${DOGFOOD_HOST_GID}"'))).toBe(true);
expect(args).toContain('alpine:3.19@sha256:6baf43584bcb78f2e5847d1de515f23499913ac9f12bdf834811a3145eb11ca1');
expect(args).toContain(`type=bind,src=${fx.root},dst=/candidate,readonly`);
expect(args).toContain('$(touch should-not-exist); spaced');
Expand Down
Loading