From edd3ccb16dc388198159d3d2bc722bbc850eb6ae Mon Sep 17 00:00:00 2001 From: Clayde Date: Fri, 20 Mar 2026 18:36:07 +0000 Subject: [PATCH 1/4] Fix #48: Make Clayde deployable by anyone - Add CLAYDE_GIT_NAME and CLAYDE_GIT_EMAIL settings to config.py; git_name defaults to github_username via effective_git_name property - Remove hardcoded defaults from github_username and whitelisted_users - Move git identity config from Dockerfile build-time RUN to a runtime entrypoint.sh that reads env vars at container startup - Update config.env.template with generic placeholders and new fields - Update CLAUDE.md to remove instance-specific values and apprise references - Update README.md with a "Deploying Your Own Instance" section and add CLAYDE_GIT_NAME/CLAYDE_GIT_EMAIL to the configuration table - Update tests to reflect removed hardcoded defaults and add coverage for the effective_git_name property Co-Authored-By: Claude Sonnet 4.6 --- CLAUDE.md | 20 ++++++++--------- Dockerfile | 12 +++++----- README.md | 52 +++++++++++++++++++++++++++++++++++++++++++- config.env.template | 18 +++++++++++---- entrypoint.sh | 22 +++++++++++++++++++ src/clayde/config.py | 10 +++++++-- tests/test_config.py | 17 +++++++++++++-- 7 files changed, 126 insertions(+), 25 deletions(-) create mode 100755 entrypoint.sh diff --git a/CLAUDE.md b/CLAUDE.md index 86e9ecb..780c9eb 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,12 +1,8 @@ # Clayde -**Name:** Clayde -**Email:** clayde@vtettenborn.net -**GitHub:** @ClaydeCode +Clayde is a persistent autonomous AI software agent running in a Docker container. My purpose is to help with software development by working on GitHub issues assigned to me. When assigned an issue, I analyze the relevant codebase, implement a solution, open a pull request, and post a comment on the issue summarizing what I did. -Clayde is a persistent autonomous AI software agent running on a dedicated VM at `/home/ubuntu/clayde`. My purpose is to help with software development by working on GitHub issues assigned to me. When assigned an issue, I analyze the relevant codebase, implement a solution, open a pull request, and post a comment on the issue summarizing what I did. - -The `gh` CLI is authenticated as @ClaydeCode and git is configured with my name and email. +The `gh` CLI is authenticated as the configured bot GitHub account and git is configured with the identity from `CLAYDE_GIT_NAME` and `CLAYDE_GIT_EMAIL`. --- @@ -27,7 +23,7 @@ The `gh` CLI is authenticated as @ClaydeCode and git is configured with my name - **Container layout:** Application code at `/opt/clayde`, data at `/data` (single volume mount from host `./data`) - **Claude:** Dual backend — Anthropic Python SDK (`api`) or Claude Code CLI (`cli`), selected by `CLAYDE_CLAUDE_BACKEND` - **Git credential helper:** `gh auth git-credential` (configured globally in the container) -- **Git identity:** `user.name = Clayde`, `user.email = clayde@vtettenborn.net` +- **Git identity:** configured at container startup from `CLAYDE_GIT_NAME` and `CLAYDE_GIT_EMAIL` env vars --- @@ -100,9 +96,11 @@ Plain `KEY=VALUE` file (no shell quoting). All keys use `CLAYDE_` prefix and are | Key | Purpose | |-----|---------| | `CLAYDE_GITHUB_TOKEN` | Fine-grained PAT with Issues R/W, Pull Requests R/W, Contents R/W | -| `CLAYDE_GITHUB_USERNAME` | `ClaydeCode` | +| `CLAYDE_GITHUB_USERNAME` | The bot account username (e.g. `YourBotName`) | | `CLAYDE_ENABLED` | Set to `true` to activate; any other value causes immediate exit | -| `CLAYDE_WHITELISTED_USERS` | Comma-separated list of trusted GitHub usernames (e.g. `max-tet,ClaydeCode`) | +| `CLAYDE_WHITELISTED_USERS` | Comma-separated list of trusted GitHub usernames | +| `CLAYDE_GIT_NAME` | Git commit author name (defaults to `CLAYDE_GITHUB_USERNAME` if not set) | +| `CLAYDE_GIT_EMAIL` | Git commit author email (required) | | `CLAYDE_CLAUDE_API_KEY` | Anthropic API key for Claude SDK calls (required when backend=`api`) | | `CLAYDE_CLAUDE_MODEL` | Model to use (default: `claude-opus-4-6`) | | `CLAYDE_CLAUDE_BACKEND` | `api` (default) or `cli` — selects Anthropic SDK or Claude Code CLI | @@ -292,9 +290,9 @@ Logger names: `clayde.orchestrator`, `clayde.tasks.plan`, `clayde.tasks.implemen ## Interactive Issue Work (`gh-issue.md`) -The file `gh-issue.md` is a Claude Code slash-command prompt (`/gh-issue `) for working on issues interactively (outside cron). It runs as a multi-step subagent workflow: Plan → clarify → implement → self-review → address review → return PR URL. Sends push notifications via `apprise ntfy://7yuau0vyes`. +The file `gh-issue.md` is a Claude Code slash-command prompt (`/gh-issue `) for working on issues interactively (outside cron). It runs as a multi-step subagent workflow: Plan → clarify → implement → self-review → address review → return PR URL. -Allowed tools for interactive work: `Bash(gh:*)`, `Bash(git:*)`, `Bash(just:*)`, `Bash(python:*)`, `Bash(pytest:*)`, `Bash(npm:*)`, `Bash(uv:*)`, `Bash(apprise:*)`, `Read`, `Write`, `Edit`, `Glob`, `Grep` +Allowed tools for interactive work: `Bash(gh:*)`, `Bash(git:*)`, `Bash(just:*)`, `Bash(python:*)`, `Bash(pytest:*)`, `Bash(npm:*)`, `Bash(uv:*)`, `Read`, `Write`, `Edit`, `Glob`, `Grep` Branch naming for interactive work: `issue/{number}-short-desc` diff --git a/Dockerfile b/Dockerfile index 755e942..65a8894 100644 --- a/Dockerfile +++ b/Dockerfile @@ -37,14 +37,16 @@ COPY src/ src/ COPY CLAUDE.md ./ RUN uv sync --frozen --no-dev +# Copy entrypoint script +COPY entrypoint.sh /opt/clayde/entrypoint.sh +RUN chmod +x /opt/clayde/entrypoint.sh + # Create data directories and set ownership RUN mkdir -p /data/repos /data/logs && chown -R clayde:clayde /data -# Switch to non-root user and configure git +# Switch to non-root user and configure git credential helper USER clayde RUN mkdir -p /home/clayde/.claude && \ - git config --global credential.helper '!gh auth git-credential' && \ - git config --global user.name "Clayde" && \ - git config --global user.email "clayde@vtettenborn.net" + git config --global credential.helper '!gh auth git-credential' -ENTRYPOINT ["/opt/clayde/.venv/bin/clayde"] +ENTRYPOINT ["/opt/clayde/entrypoint.sh"] diff --git a/README.md b/README.md index 78d752d..3861af7 100644 --- a/README.md +++ b/README.md @@ -144,9 +144,59 @@ Clayde will start its loop, checking for assigned issues every 5 minutes (config | Key | Purpose | |---|---| | `CLAYDE_GITHUB_TOKEN` | Fine-grained PAT (Issues R/W, PRs R/W, Contents R/W) | -| `CLAYDE_GITHUB_USERNAME` | `ClaydeCode` | +| `CLAYDE_GITHUB_USERNAME` | The bot account username | +| `CLAYDE_GIT_NAME` | Git commit author name (defaults to `CLAYDE_GITHUB_USERNAME` if not set) | +| `CLAYDE_GIT_EMAIL` | Git commit author email (required) | | `CLAYDE_ENABLED` | Set to `true` to activate | | `CLAYDE_WHITELISTED_USERS` | Comma-separated trusted GitHub usernames | | `CLAYDE_CLAUDE_BACKEND` | `api` (default) or `cli` | | `CLAYDE_CLAUDE_API_KEY` | Anthropic API key (required when backend=`api`) | | `CLAYDE_CLAUDE_MODEL` | Model to use (default: `claude-opus-4-6`) | + +--- + +## Deploying Your Own Instance + +Clayde is designed to be deployed by anyone. To run your own instance: + +### 1. Create a dedicated bot GitHub account + +Create a GitHub account for your bot (e.g. `my-bot`). This is the account that will be assigned issues and open pull requests. + +### 2. Create a GitHub Personal Access Token for the bot + +From the bot account, create a fine-grained PAT with the following repository permissions: +- **Issues**: Read and Write +- **Pull requests**: Read and Write +- **Contents**: Read and Write + +### 3. Configure the instance + +```bash +mkdir -p data/logs data/repos +cp config.env.template data/config.env +``` + +Edit `data/config.env`: + +``` +CLAYDE_GITHUB_TOKEN=github_pat_... +CLAYDE_GITHUB_USERNAME=my-bot +CLAYDE_GIT_EMAIL=my-bot@example.com +CLAYDE_ENABLED=true +CLAYDE_WHITELISTED_USERS=your-username,my-bot +``` + +### 4. Choose a Claude backend and start + +Follow the backend instructions in the [Setup](#setup) section above, then run: + +```bash +docker compose up -d +``` + +### 5. Assign issues to your bot + +In any repository the bot has access to, assign issues to the bot account. Clayde will pick them up automatically on the next loop cycle. + +> **Migration note:** If you previously deployed an older version of Clayde with hardcoded identity values (`ClaydeCode`, `max-tet`, `clayde@vtettenborn.net`), you must now set `CLAYDE_GITHUB_USERNAME`, `CLAYDE_GIT_EMAIL`, and `CLAYDE_WHITELISTED_USERS` explicitly in `data/config.env`. diff --git a/config.env.template b/config.env.template index 89f67dd..38f3227 100644 --- a/config.env.template +++ b/config.env.template @@ -1,10 +1,20 @@ -# Clayde configuration — copy to config.env and fill in values +# Clayde configuration — copy to data/config.env and fill in your values + +# GitHub identity: the bot account Clayde runs as +CLAYDE_GITHUB_USERNAME=your-bot-username CLAYDE_GITHUB_TOKEN= -CLAYDE_GITHUB_USERNAME=ClaydeCode + +# Git identity for commits (name defaults to CLAYDE_GITHUB_USERNAME if not set) +CLAYDE_GIT_NAME= +CLAYDE_GIT_EMAIL=your-bot@example.com + +# Set to true to activate Clayde; any other value causes immediate exit CLAYDE_ENABLED=false -CLAYDE_WHITELISTED_USERS_RAW=max-tet,ClaydeCode + +# Comma-separated list of trusted GitHub usernames allowed to approve plans +CLAYDE_WHITELISTED_USERS=your-username,your-bot-username # Claude backend: "api" (Anthropic SDK, requires CLAYDE_CLAUDE_API_KEY) -# "cli" (Claude Code CLI, requires OAuth credentials mounted) +# "cli" (Claude Code CLI, requires OAuth credentials mounted) CLAYDE_CLAUDE_BACKEND=api CLAYDE_CLAUDE_API_KEY= diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100755 index 0000000..43fa768 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,22 @@ +#!/bin/sh +# Configure git identity from environment variables at container startup. +# CLAYDE_GIT_NAME defaults to CLAYDE_GITHUB_USERNAME if not set. +# CLAYDE_GIT_EMAIL is required. + +GIT_NAME="${CLAYDE_GIT_NAME:-$CLAYDE_GITHUB_USERNAME}" +GIT_EMAIL="${CLAYDE_GIT_EMAIL}" + +if [ -z "$GIT_NAME" ]; then + echo "ERROR: CLAYDE_GIT_NAME (or CLAYDE_GITHUB_USERNAME) must be set" >&2 + exit 1 +fi + +if [ -z "$GIT_EMAIL" ]; then + echo "ERROR: CLAYDE_GIT_EMAIL must be set" >&2 + exit 1 +fi + +git config --global user.name "$GIT_NAME" +git config --global user.email "$GIT_EMAIL" + +exec /opt/clayde/.venv/bin/clayde "$@" diff --git a/src/clayde/config.py b/src/clayde/config.py index c879566..7c1c7d9 100644 --- a/src/clayde/config.py +++ b/src/clayde/config.py @@ -20,12 +20,18 @@ class Settings(BaseSettings): ) github_token: str = "" - github_username: str = "ClaydeCode" + github_username: str = "" enabled: bool = False - whitelisted_users: str = "max-tet,ClaydeCode" + whitelisted_users: str = "" claude_api_key: str = "" claude_model: str = "claude-opus-4-6" claude_backend: str = "api" # "api" or "cli" + git_name: str = "" + git_email: str = "" + + @property + def effective_git_name(self) -> str: + return self.git_name or self.github_username # Claude invocation tuning claude_tool_loop_timeout_s: int = 1800 diff --git a/tests/test_config.py b/tests/test_config.py index abd6116..bb670fb 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -22,11 +22,24 @@ def test_defaults(self, monkeypatch): monkeypatch.delenv("CLAYDE_GITHUB_TOKEN", raising=False) monkeypatch.delenv("CLAYDE_ENABLED", raising=False) monkeypatch.delenv("CLAYDE_WHITELISTED_USERS", raising=False) + monkeypatch.delenv("CLAYDE_GITHUB_USERNAME", raising=False) s = Settings(_env_file=None) assert s.github_token == "" assert s.enabled is False - assert s.github_username == "ClaydeCode" - assert s.whitelisted_users_list == ["max-tet", "ClaydeCode"] + assert s.github_username == "" + assert s.whitelisted_users_list == [] + + def test_effective_git_name_falls_back_to_username(self, monkeypatch): + monkeypatch.setenv("CLAYDE_GITHUB_USERNAME", "my-bot") + monkeypatch.delenv("CLAYDE_GIT_NAME", raising=False) + s = Settings(_env_file=None) + assert s.effective_git_name == "my-bot" + + def test_effective_git_name_uses_explicit_value(self, monkeypatch): + monkeypatch.setenv("CLAYDE_GITHUB_USERNAME", "my-bot") + monkeypatch.setenv("CLAYDE_GIT_NAME", "My Bot") + s = Settings(_env_file=None) + assert s.effective_git_name == "My Bot" def test_loads_from_env_file(self, tmp_path, monkeypatch): env_file = tmp_path / "config.env" From 45c16acf65e74a4a7b9d61fd43ea3c7e48796de4 Mon Sep 17 00:00:00 2001 From: Clayde Date: Fri, 20 Mar 2026 19:43:17 +0000 Subject: [PATCH 2/4] Address review: fix token type, remove gh-issue.md section, remove migration note - CLAUDE.md: change "Fine-grained PAT" to "Classic PAT with full repo scope" - CLAUDE.md: remove the "Interactive Issue Work (gh-issue.md)" section entirely - README.md: change "fine-grained PAT" to "classic personal access token with full repo scope" - README.md: remove migration note (to be posted as a PR comment instead) Co-Authored-By: Claude Sonnet 4.6 --- CLAUDE.md | 12 +----------- README.md | 6 +----- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 780c9eb..d7da785 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -95,7 +95,7 @@ Plain `KEY=VALUE` file (no shell quoting). All keys use `CLAYDE_` prefix and are | Key | Purpose | |-----|---------| -| `CLAYDE_GITHUB_TOKEN` | Fine-grained PAT with Issues R/W, Pull Requests R/W, Contents R/W | +| `CLAYDE_GITHUB_TOKEN` | Classic PAT with full `repo` scope | | `CLAYDE_GITHUB_USERNAME` | The bot account username (e.g. `YourBotName`) | | `CLAYDE_ENABLED` | Set to `true` to activate; any other value causes immediate exit | | `CLAYDE_WHITELISTED_USERS` | Comma-separated list of trusted GitHub usernames | @@ -288,16 +288,6 @@ Logger names: `clayde.orchestrator`, `clayde.tasks.plan`, `clayde.tasks.implemen --- -## Interactive Issue Work (`gh-issue.md`) - -The file `gh-issue.md` is a Claude Code slash-command prompt (`/gh-issue `) for working on issues interactively (outside cron). It runs as a multi-step subagent workflow: Plan → clarify → implement → self-review → address review → return PR URL. - -Allowed tools for interactive work: `Bash(gh:*)`, `Bash(git:*)`, `Bash(just:*)`, `Bash(python:*)`, `Bash(pytest:*)`, `Bash(npm:*)`, `Bash(uv:*)`, `Read`, `Write`, `Edit`, `Glob`, `Grep` - -Branch naming for interactive work: `issue/{number}-short-desc` - ---- - ## Testing Run the test suite after any feature development or bug fix: diff --git a/README.md b/README.md index 3861af7..b64642f 100644 --- a/README.md +++ b/README.md @@ -165,10 +165,7 @@ Create a GitHub account for your bot (e.g. `my-bot`). This is the account that w ### 2. Create a GitHub Personal Access Token for the bot -From the bot account, create a fine-grained PAT with the following repository permissions: -- **Issues**: Read and Write -- **Pull requests**: Read and Write -- **Contents**: Read and Write +From the bot account, create a classic personal access token with the full **`repo`** scope. ### 3. Configure the instance @@ -199,4 +196,3 @@ docker compose up -d In any repository the bot has access to, assign issues to the bot account. Clayde will pick them up automatically on the next loop cycle. -> **Migration note:** If you previously deployed an older version of Clayde with hardcoded identity values (`ClaydeCode`, `max-tet`, `clayde@vtettenborn.net`), you must now set `CLAYDE_GITHUB_USERNAME`, `CLAYDE_GIT_EMAIL`, and `CLAYDE_WHITELISTED_USERS` explicitly in `data/config.env`. From a01104a7839a6eb8e99743f379c784c1c183cb6d Mon Sep 17 00:00:00 2001 From: Max von Tettenborn Date: Fri, 20 Mar 2026 20:47:13 +0100 Subject: [PATCH 3/4] remove another mention of gh-issue.md --- CLAUDE.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index d7da785..33e98f8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -35,7 +35,6 @@ pyproject.toml # hatchling build; console scripts: clayde, clayde-once CLAUDE.md # this file — identity + project context Dockerfile # Python 3.13-slim image with git, gh, uv docker-compose.yml # container deployment config -gh-issue.md # slash-command prompt for interactive issue work uv.lock src/clayde/ __init__.py From aaf10d9aeb1c3edd9b98f99fbab2b6cc02b53bf9 Mon Sep 17 00:00:00 2001 From: Max von Tettenborn Date: Fri, 20 Mar 2026 20:49:17 +0100 Subject: [PATCH 4/4] fix another mention of fine-grained token --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b64642f..5b875d5 100644 --- a/README.md +++ b/README.md @@ -143,7 +143,7 @@ Clayde will start its loop, checking for assigned issues every 5 minutes (config | Key | Purpose | |---|---| -| `CLAYDE_GITHUB_TOKEN` | Fine-grained PAT (Issues R/W, PRs R/W, Contents R/W) | +| `CLAYDE_GITHUB_TOKEN` | Classic PAT with full `repo` scope | | `CLAYDE_GITHUB_USERNAME` | The bot account username | | `CLAYDE_GIT_NAME` | Git commit author name (defaults to `CLAYDE_GITHUB_USERNAME` if not set) | | `CLAYDE_GIT_EMAIL` | Git commit author email (required) |