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
68 changes: 68 additions & 0 deletions .github/workflows/release-images.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: Release Images

on:
release:
types: [published]
workflow_dispatch:
inputs:
tag:
description: Image tag (defaults to latest)
required: false
default: latest

permissions:
contents: read
packages: write

env:
REGISTRY: ghcr.io
GATEWAY_IMAGE: ghcr.io/jwbron/egg-gateway
SANDBOX_IMAGE: ghcr.io/jwbron/egg-sandbox

jobs:
build-and-push:
name: Build and Push Images
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Set image tag
id: tag
run: |
if [[ "${{ github.event_name }}" == "release" ]]; then
echo "tag=${{ github.event.release.tag_name }}" >> "$GITHUB_OUTPUT"
else
echo "tag=${{ inputs.tag }}" >> "$GITHUB_OUTPUT"
fi

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

- name: Build and push gateway image
uses: docker/build-push-action@v6
with:
context: .
file: gateway/Dockerfile
push: true
platforms: linux/amd64
tags: |
${{ env.GATEWAY_IMAGE }}:${{ steps.tag.outputs.tag }}
${{ env.GATEWAY_IMAGE }}:latest

- name: Build and push sandbox image
uses: docker/build-push-action@v6
with:
context: .
file: sandbox/Dockerfile
push: true
platforms: linux/amd64
tags: |
${{ env.SANDBOX_IMAGE }}:${{ steps.tag.outputs.tag }}
${{ env.SANDBOX_IMAGE }}:latest
105 changes: 105 additions & 0 deletions .github/workflows/test-action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
name: Test Action

on:
pull_request:
branches: [main]
paths:
- "action/**"
- ".github/workflows/test-action.yml"
workflow_dispatch:

jobs:
config-generation:
name: Config Generation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Test generate-config.sh
env:
INPUT_ANTHROPIC_OAUTH_TOKEN: test-token-placeholder
INPUT_GITHUB_TOKEN: test-github-token
INPUT_BOT_USERNAME: egg
RUNNER_TEMP: ${{ runner.temp }}
run: |
bash action/generate-config.sh

# GITHUB_* vars are built-in GHA variables that cannot be overridden,
# so we validate against the actual runtime values
CONFIG_DIR="${RUNNER_TEMP}/egg-config-${GITHUB_RUN_ID}"
REPO_NAME="${GITHUB_REPOSITORY#*/}"

echo "=== Validating repositories.yaml ==="
cat "$CONFIG_DIR/repositories.yaml"

# Verify required fields use actual GHA context values
# Use -F for fixed string matching — GITHUB_ACTOR may contain
# regex-special characters (e.g., "app[bot]")
grep -qF "github_username: ${GITHUB_ACTOR}" "$CONFIG_DIR/repositories.yaml"
grep -qF "bot_username: egg" "$CONFIG_DIR/repositories.yaml"
grep -qF "${GITHUB_REPOSITORY}" "$CONFIG_DIR/repositories.yaml"
grep -qF "auth_mode: user" "$CONFIG_DIR/repositories.yaml"
grep -qF "/home/egg/repos/${REPO_NAME}" "$CONFIG_DIR/repositories.yaml"

echo "=== Validating secrets.env ==="
# Don't cat secrets, just verify they exist
test -f "$CONFIG_DIR/secrets.env"
grep -q "CLAUDE_CODE_OAUTH_TOKEN=" "$CONFIG_DIR/secrets.env"
# GITHUB_USER_TOKEN should NOT be in secrets.env — gateway reads it
# from the environment variable, not from this file
if grep -q "GITHUB_USER_TOKEN=" "$CONFIG_DIR/secrets.env"; then
echo "ERROR: GITHUB_USER_TOKEN should not be in secrets.env"
exit 1
fi

echo "=== Validating launcher-secret ==="
test -f "$CONFIG_DIR/launcher-secret"
test -s "$CONFIG_DIR/launcher-secret"

echo "All config generation tests passed"

- name: Test generate-config.sh with bot token
env:
INPUT_ANTHROPIC_OAUTH_TOKEN: test-token-placeholder
INPUT_GITHUB_TOKEN: test-github-token
INPUT_BOT_GITHUB_TOKEN: test-bot-token
INPUT_BOT_USERNAME: my-bot
RUNNER_TEMP: ${{ runner.temp }}
run: |
# Clean up config from previous step so we validate fresh output
rm -rf "${RUNNER_TEMP}/egg-config-${GITHUB_RUN_ID}"

bash action/generate-config.sh

CONFIG_DIR="${RUNNER_TEMP}/egg-config-${GITHUB_RUN_ID}"

grep -q "bot_username: my-bot" "$CONFIG_DIR/repositories.yaml"
grep -q "auth_mode: bot" "$CONFIG_DIR/repositories.yaml"
grep -q "BOT_GITHUB_TOKEN=" "$CONFIG_DIR/secrets.env"

echo "Bot token config generation tests passed"

shellcheck:
name: Shellcheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Run shellcheck on action scripts
run: |
shellcheck --severity=warning action/entrypoint.sh action/generate-config.sh

integration-test:
name: Integration Test
runs-on: ubuntu-latest
if: github.event_name == 'workflow_dispatch'
steps:
- uses: actions/checkout@v4

- name: Run action (smoke test)
uses: ./action
with:
prompt: "Echo 'Hello from egg' and exit successfully"
anthropic-oauth-token: ${{ secrets.ANTHROPIC_OAUTH_TOKEN }}
github-token: ${{ secrets.GITHUB_TOKEN }}
timeout: "5"
70 changes: 70 additions & 0 deletions action/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
name: egg
description: Run egg (autonomous coding agent) in GitHub Actions
branding:
icon: terminal
color: orange

inputs:
prompt:
description: Task prompt for Claude Code
required: true
anthropic-oauth-token:
description: Anthropic OAuth token for Claude API
required: true
github-token:
description: GitHub token for git operations
required: true
default: ${{ github.token }}
bot-github-token:
description: Bot GitHub App token (optional, for bot identity)
required: false
bot-username:
description: Bot GitHub username for filtering self-comments
required: false
default: egg
mode:
description: "Network mode: public, private, or auto (auto-detects from repo visibility)"
required: false
default: auto
timeout:
description: Timeout in minutes
required: false
default: "30"
model:
description: Claude model to use
required: false
default: opus
image-tag:
description: Docker image tag to use (e.g., v1.0.0 or latest)
required: false
default: latest

outputs:
exit-code:
description: Sandbox container exit code (0 = success)
value: ${{ steps.run.outputs.exit-code }}
pr-url:
description: URL of created PR, if any
value: ${{ steps.run.outputs.pr-url }}
log-file:
description: Path to full Claude output log
value: ${{ steps.run.outputs.log-file }}

runs:
using: composite
steps:
- name: Run egg
id: run
shell: bash
env:
INPUT_PROMPT: ${{ inputs.prompt }}
INPUT_ANTHROPIC_OAUTH_TOKEN: ${{ inputs.anthropic-oauth-token }}
INPUT_GITHUB_TOKEN: ${{ inputs.github-token }}
INPUT_BOT_GITHUB_TOKEN: ${{ inputs.bot-github-token }}
INPUT_BOT_USERNAME: ${{ inputs.bot-username }}
INPUT_MODE: ${{ inputs.mode }}
INPUT_TIMEOUT: ${{ inputs.timeout }}
INPUT_MODEL: ${{ inputs.model }}
INPUT_IMAGE_TAG: ${{ inputs.image-tag }}
GITHUB_EVENT_REPOSITORY_VISIBILITY: ${{ github.event.repository.visibility }}
run: ${{ github.action_path }}/entrypoint.sh
Loading
Loading