Skip to content
Open
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
127 changes: 127 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# Claudio — CLAUDE.md

## Project Overview

**Claudio** is an OCI container image that packages Claude Code CLI into a portable, CI/CD-friendly container. It uses Google Vertex AI as the Claude API backend by default and bundles skills/plugins from the [claudio-skills](https://github.com/aipcc-cicd/claudio-skills) marketplace.

Primary use cases:
- Running Claude Code in GitLab CI pipelines via a reusable `.claudio` job template
- Providing a portable AI development assistant that can be mounted into any working directory

## Key Files

| File | Purpose |
|---|---|
| `Containerfile` | Multi-stage OCI image build (preparer → final) |
| `entrypoint.sh` | Container startup: configures git identity, SSH signing key, gcloud auth, starts MemPalace if `MEMPAL_DIR` is set |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Inconsistent variable name in documentation.

Line 16 mentions MEMPAL_DIR but the actual variable used throughout README.md and the implementation is MEMPAL_ENABLED. This could confuse users.

Proposed fix
-| `entrypoint.sh` | Container startup: configures git identity, SSH signing key, gcloud auth, starts MemPalace if `MEMPAL_DIR` is set |
+| `entrypoint.sh` | Container startup: configures git identity, SSH signing key, gcloud auth, starts MemPalace if `MEMPAL_ENABLED` is set |
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
| `entrypoint.sh` | Container startup: configures git identity, SSH signing key, gcloud auth, starts MemPalace if `MEMPAL_DIR` is set |
| `entrypoint.sh` | Container startup: configures git identity, SSH signing key, gcloud auth, starts MemPalace if `MEMPAL_ENABLED` is set |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CLAUDE.md` at line 16, Update the documentation entry referencing the
environment variable for starting MemPalace: replace the incorrect `MEMPAL_DIR`
with the actual variable `MEMPAL_ENABLED` (or vice-versa if you prefer to change
the implementation) so docs and code match; check `entrypoint.sh`, README.md,
and CLAUDE.md for any other occurrences of `MEMPAL_DIR` and ensure they
consistently use `MEMPAL_ENABLED` (or update the code to use `MEMPAL_DIR`) and
keep the descriptive text about starting MemPalace unchanged.

| `Makefile` | Build, tag, push, and release automation |
| `conf/.claude.json` | Claude client config template baked into the image |
| `scripts/generate-plugin-configs.sh` | Registers claudio-skills as Claude plugins at build time |
| `integrations/gitlab-ci/claudio.yml` | Generated GitLab CI reusable job template |
| `.github/workflows/build.yml` | Multi-arch (amd64 + arm64) CI build pipeline |
| `.github/workflows/push-pr-images.yml` | Publishes PR images to GHCR |
| `renovate.json` | Automated dependency updates (Claude Code, gcloud SDK versions) |

## Build & Run

### Build the image

```bash
make oci-build # native arch, defaults
CONTAINER_MANAGER=docker make oci-build # use Docker instead of Podman

# Pin to a specific claudio-skills ref
CS_REF_TYPE=tag CS_REF=v0.1.0 make oci-build
CS_REF_TYPE=branch CS_REF=main make oci-build
CS_REF_TYPE=pr CS_REF=9 make oci-build

# Custom image repo/tag
IMAGE_REPO=ghcr.io/myorg/claudio IMAGE_TAG=latest make oci-build
```

### Run locally

```bash
podman run -it --rm --userns=keep-id \
-v ${HOME}/.config/gcloud:/home/claudio/.config/gcloud \
-v ${PWD}:/home/claudio/workdir \
-e ANTHROPIC_VERTEX_PROJECT_ID="my-gcp-project" \
quay.io/aipcc-cicd/claudio:latest
```

## Architecture

- **Base images**: `ubi10` (preparer) → `ubi10/python-312-minimal` (final)
- **Key installed tools**: Claude Code CLI, Google Cloud SDK, Podman, Skopeo, git, jq
- **Runs as**: non-root user `claudio` (uid 1000)
- **Multi-arch**: amd64 and arm64 manifests published to GHCR (dev/PRs) and Quay (releases)

## Dependencies & Versions (managed by Renovate)

- Claude Code CLI: pinned in `Containerfile` (`ARG CLAUDE_CODE_VERSION`)
- Google Cloud SDK: pinned in `Containerfile` (`ARG GCLOUD_SDK_VERSION`)
- claudio-skills: resolved at build time via `CS_REF`/`CS_REF_TYPE`; cache invalidated automatically via `CS_CACHE_KEY` (resolved commit SHA)

## Release Process

1. Bump `VERSION` in `Makefile`
2. Tag the commit: `make tag` (creates `v<VERSION>` git tag)
3. Push the tag — the `build.yml` workflow publishes to Quay and GHCR

Current version: `0.6.0-dev`

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Version mismatch with Makefile.

The documented version 0.6.0-dev doesn't match the VERSION in Makefile which is 0.6.1-dev.

Proposed fix
-Current version: `0.6.0-dev`
+Current version: `0.6.1-dev`
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Current version: `0.6.0-dev`
Current version: `0.6.1-dev`
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CLAUDE.md` at line 71, Update the version string in CLAUDE.md to match the
Makefile VERSION value; specifically change the current line containing "Current
version: `0.6.0-dev`" to reflect `0.6.1-dev` (or alternatively update the
Makefile VERSION to `0.6.0-dev` if the Makefile is wrong) so the documented
version and the VERSION variable are consistent.


## CI/CD

- **PRs**: builds both arches, uploads tar artifacts, `push-pr-images.yml` loads and pushes to GHCR with PR tag
- **`main` push**: builds + pushes `latest` multi-arch manifest to GHCR
- **Version tags**: builds + pushes versioned multi-arch manifest to Quay (`quay.io/aipcc-cicd/claudio`)

## GitLab CI Integration

Users include the reusable template and extend `.claudio`:

```yaml
include:
- project: 'aipcc-cicd/claudio'
file: 'integrations/gitlab-ci/claudio.yml'

my-job:
extends: .claudio
variables:
CLAUDIO_PROMPT: "Review this MR and suggest improvements"
```

## Memory (MemPalace)

Persistent memory is provided by [MemPalace](https://github.com/MemPalace/mempalace) and is **opt-in** via the `MEMPAL_ENABLED` env var.

The palace is stored at a **fixed path inside the container**: `/home/claudio/.mempalace/palace`. Mount a persistent volume there to retain memory across sessions.

| Variable | Default | Purpose |
|---|---|---|
| `MEMPAL_ENABLED` | `"false"` (disabled) | Enable flag for MemPalace hooks. Set to `true` to activate. The palace is always at `/home/claudio/.mempalace/palace` — mount a volume there to persist memory across runs. |
| `MEMPAL_SAVE_INTERVAL` | `5` | Number of conversation exchanges between automatic memory saves on shutdown (passed to `mempal_save_hook.sh`). Lower values save more frequently; higher values reduce overhead. |

Two hooks are always registered in `conf/.claude/settings.json` but short-circuit with `exit 0` when `MEMPAL_ENABLED` is unset:

- **PreCompact** → `mempal_precompact_hook.sh` — saves context before compaction
- **Stop** → `mempal_save_hook.sh` — auto-saves every N exchanges on shutdown

Hook scripts are downloaded at build time from the `v${MEMPALACE_V}` tag of the upstream repo, keeping them in sync with the installed pip package version. Renovate bumps `MEMPALACE_V` in the Containerfile and the hook URLs update automatically.

### Local usage with memory

```bash
podman run -it --rm --userns=keep-id \
-v ${HOME}/.config/gcloud:/home/claudio/.config/gcloud \
-v ${PWD}:/home/claudio/workdir \
-v ${HOME}/.local/share/claudio-memory:/home/claudio/.mempalace/palace \
-e ANTHROPIC_VERTEX_PROJECT_ID="..." \
-e MEMPAL_ENABLED=true \
-e MEMPAL_SAVE_INTERVAL=5 \
quay.io/aipcc-cicd/claudio:v0.6.0
```

## License

Apache 2.0
16 changes: 15 additions & 1 deletion Containerfile
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,10 @@ ENV HOME /home/claudio
ENV PATH="${HOME}/.local/bin:${PATH}"

# Base for claudio image
RUN microdnf install -y skopeo podman unzip gzip git jq; \
# pyopenssl is pulled in transitively by the UBI10 base image and is not a direct
# dependency. CVE-2026-27459 (CRITICAL) affects <26.0.0; pin the fixed version.
RUN microdnf install -y skopeo podman unzip gzip git jq && microdnf clean all; \
pip install --no-cache-dir "pyopenssl>=26.0.0"; \
useradd claudio

# Claude
Expand Down Expand Up @@ -93,6 +96,17 @@ RUN echo "cs-cache-key: ${CS_CACHE_KEY}" \
claude plugin install --scope user claudio-plugin; \
pt-manager.sh


# MemPalace
ENV MEMPALACE_V 3.4.1
ENV MEMPAL_SAVE_INTERVAL 5
RUN pip install --no-cache-dir MemPalace==${MEMPALACE_V}; \
claude plugin marketplace add MemPalace/mempalace@v${MEMPALACE_V};\
claude plugin install --scope user mempalace; \
grep -q '^SAVE_INTERVAL=[0-9]' ${HOME}/.claude/plugins/marketplaces/mempalace/hooks/mempal_save_hook.sh || \
{ echo 'ERROR: SAVE_INTERVAL pattern not found in hook script'; exit 1; }; \
sed -i 's/^SAVE_INTERVAL=[0-9]\+/SAVE_INTERVAL="${MEMPAL_SAVE_INTERVAL:-15}"/' ${HOME}/.claude/plugins/marketplaces/mempalace/hooks/mempal_save_hook.sh;
Comment on lines +102 to +108

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Inconsistent default values for MEMPAL_SAVE_INTERVAL.

The ENV MEMPAL_SAVE_INTERVAL 5 sets the container default to 5, but the sed replacement on line 105 uses ${MEMPAL_SAVE_INTERVAL:-15} as a fallback. While the ENV ensures the runtime default is effectively 5, the fallback of 15 in the script is confusing and inconsistent with the documented default.

Consider aligning the fallback value:

Proposed fix
-    sed -i 's/^SAVE_INTERVAL=[0-9]\+/SAVE_INTERVAL="${MEMPAL_SAVE_INTERVAL:-15}"/' ${HOME}/.claude/plugins/marketplaces/mempalace/hooks/mempal_save_hook.sh;
+    sed -i 's/^SAVE_INTERVAL=[0-9]\+/SAVE_INTERVAL="${MEMPAL_SAVE_INTERVAL:-5}"/' ${HOME}/.claude/plugins/marketplaces/mempalace/hooks/mempal_save_hook.sh;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Containerfile` around lines 99 - 105, The Dockerfile sets ENV
MEMPAL_SAVE_INTERVAL to 5 but the sed replacement in the mempal_save_hook.sh
uses a different fallback (${MEMPAL_SAVE_INTERVAL:-15}), causing inconsistent
defaults; update the sed command to use the same default (change
${MEMPAL_SAVE_INTERVAL:-15} to ${MEMPAL_SAVE_INTERVAL:-5}) or alternatively
change ENV MEMPAL_SAVE_INTERVAL to 15 so both places match; modify the sed
invocation that targets SAVE_INTERVAL in
${HOME}/.claude/plugins/marketplaces/mempalace/hooks/mempal_save_hook.sh so its
fallback aligns with the ENV value.


# Claudio
RUN chown -R claudio:0 ${HOME}; \
chmod -R ug+rwx ${HOME}
Expand Down
61 changes: 61 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,67 @@ cd ~/my-project
claudio
```

## Memory

Claudio bundles [MemPalace](https://github.com/MemPalace/mempalace) for persistent memory across sessions. Memory is **opt-in** — it is disabled by default and only activates when `MEMPAL_ENABLED` is set.

The palace is stored at a **fixed path inside the container**: `/home/claudio/.mempalace/palace`. Mount a persistent volume there to retain memory across runs.

Two Claude Code hooks activate automatically:

| Hook | Event | Purpose |
|---|---|---|
| `mempal_precompact_hook.sh` | `PreCompact` | Saves context before conversation compaction |
| `mempal_save_hook.sh` | `Stop` | Auto-saves memory every N exchanges on shutdown |

| Variable | Default | Purpose |
|---|---|---|
| `MEMPAL_SAVE_INTERVAL` | `5` | Number of conversation exchanges between automatic memory saves. Lower values save more frequently; higher values reduce overhead. |

### Enabling memory locally

Mount a host directory at the palace path and set `MEMPAL_ENABLED`:

```bash
podman run -it --rm --userns=keep-id \
-v ${HOME}/.config/gcloud:/home/claudio/.config/gcloud \
-v ${PWD}:/home/claudio/workdir \
-v ${HOME}/.local/share/claudio-memory:/home/claudio/.mempalace/palace \
-e ANTHROPIC_VERTEX_PROJECT_ID="${ANTHROPIC_VERTEX_PROJECT_ID}" \
-e ANTHROPIC_VERTEX_PROJECT_QUOTA="${ANTHROPIC_VERTEX_PROJECT_QUOTA}" \
-e MEMPAL_ENABLED=true \
-e MEMPAL_SAVE_INTERVAL=5 \
quay.io/aipcc-cicd/claudio:v0.6.0
```

Add it to your wrapper script and `.env` file to make it permanent:

```bash
# in ~/.config/claudio/.env
MEMPAL_SAVE_INTERVAL=5
```

```bash
# in the wrapper script, add the volume mount and env vars
-v ${HOME}/.local/share/claudio-memory:/home/claudio/.mempalace/palace \
-e MEMPAL_ENABLED="${MEMPAL_ENABLED:-false}" \
-e MEMPAL_SAVE_INTERVAL="${MEMPAL_SAVE_INTERVAL:-5}" \
```

### Enabling memory in GitLab CI

Mount a persistent volume at the palace path and pass `MEMPAL_ENABLED`:

```yaml
my-claudio-job:
extends: .claudio
variables:
CLAUDIO_PROMPT: "Analyze the latest MRs and report to Slack"
MEMPAL_ENABLED: "true"
volumes:
- claudio-memory:/home/claudio/.mempalace/palace
```

## One-time Prompt

```bash
Expand Down
6 changes: 1 addition & 5 deletions conf/.claude/context.d/CLAUDE-base.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
# Sequential Thinking Memory
- To inspect container images or container registries you can use skopeo
- To interact with k8s / OpenShift Cluster you can use kubectl
- To create Konflux production releases you can use your konflux-release skill
- To interact with slack you can use your slack mcp server
- To interact with gitlab you can use your gitlab skill
- Before giving a final answer, summarize reasoning and list assumptions.
- When coding, first outline the approach → then show the code → then explain potential pitfalls.
- Carry forward context from earlier turns unless I override it.
- If refining prior work, build incrementally (don’t reinvent unless asked).
- Propose at least one alternative path or trade-off if relevant.
- When unsure, ask clarifying questions rather than guessing.
- Regularly checkpoint: summarize progress and agreed decisions before moving on.
- When running scripts under /home/claudio/claudio-skills/, always use absolute paths (never `cd ... && ./script`) so the Bash permission rule matches.
38 changes: 38 additions & 0 deletions conf/.claude/context.d/CLAUDE-memory.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Memory Protocol — MemPalace

MemPalace is the sole memory store. Never write to Claude Code's native auto-memory (`.md` files).

## On Wake-Up

**MANDATORY: On the first user message of every new conversation, before responding to anything else, run steps 1–3 below. Do not wait to be asked. Do not send any text response until all steps are complete.**

⚠ MemPalace tools are deferred — their schemas are NOT loaded at startup.
Before calling any `mempalace_*` tool you MUST first run:

```
ToolSearch(query: "select:mcp__plugin_mempalace_mempalace__mempalace_status,mcp__plugin_mempalace_mempalace__mempalace_diary_read")
```
Comment on lines +12 to +14

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add a language identifier to the fenced code block (MD040).

This currently violates markdownlint and can fail docs quality gates.

Suggested fix
-```
+```text
 ToolSearch(query: "select:mcp__plugin_mempalace_mempalace__mempalace_status,mcp__plugin_mempalace_mempalace__mempalace_diary_read")
</details>

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 markdownlint-cli2 (0.22.1)</summary>

[warning] 12-12: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

</details>

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @conf/.claude/context.d/CLAUDE-memory.md around lines 12 - 14, The fenced
code block containing the ToolSearch call lacks a language identifier (MD040);
update the triple-backtick fence that wraps the line starting with
ToolSearch(...) to include a language tag such as text (e.g., ```text) so
markdownlint passes; ensure the opening fence immediately preceding the
ToolSearch(...) line is changed and the closing fence remains intact.


</details>

<!-- fingerprinting:phantom:triton:hawk -->

<!-- d98c2f50 -->

<!-- This is an auto-generated comment by CodeRabbit -->


Then immediately call both tools **in parallel**:

1. `mempalace_status` — palace overview
2. `mempalace_diary_read` (agent_name: "session-hook", last_n: 5) — recent session checkpoints (auto-saved by plugin)
3. `mempalace_search` (query: "<topic>") — before answering about past work
Comment thread
coderabbitai[bot] marked this conversation as resolved.

## After Completing Work (MANDATORY)

After completing any of the following, call `mempalace_add_drawer` immediately — do not wait until end of session:

- Jira operations: cards created, updated, transitioned, or linked (save keys + summaries)
- Decisions made (architecture, approach, naming)
- Config or settings changes (CLAUDE.md, settings.json, hooks)
- Scripts or tools created or modified

Save the **outcome** (what was done, keys created, values set) — not the process. The stop hook saves the transcript; this saves the facts.

## Rules

- Search before answering about past work — never guess.
- **Before generating any artifact** (document, plan, summary, config, script) search MemPalace first. If a prior version exists, retrieve and present it — do not regenerate from inference.
- If a fact changes: `kg_invalidate` then `kg_add`.
- Checkpoints are raw message extracts written by the plugin automatically under `agent_name: "session-hook"`. They capture what was said but not curated findings.
4 changes: 4 additions & 0 deletions conf/.claude/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
"model": "claude-sonnet-4-6",
"permissions": {
"allow": [
"mcp__plugin_mempalace_mempalace__mempalace_status",
"mcp__plugin_mempalace_mempalace__mempalace_diary_read",
"mcp__plugin_mempalace_mempalace__mempalace_search",
"mcp__plugin_mempalace_mempalace__mempalace_add_drawer"
],
"deny": [],
"ask": []
Expand Down
3 changes: 3 additions & 0 deletions conf/.mempalace/config.json
Comment thread
adrianriobo marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"palace_path": "/home/claudio/.mempalace/palace"
}
13 changes: 13 additions & 0 deletions renovate.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,19 @@
"datasourceTemplate": "github-releases",
"depNameTemplate": "aipcc-cicd/claudio-skills",
"autoReplaceStringTemplate": "CS_REF ?= {{newValue}}"
},
{
"customType": "regex",
"description": "Update MemPalace version",
"managerFilePatterns": [
"Containerfile"
],
"matchStrings": [
"ENV\\s+MEMPALACE_V\\s+(?<currentValue>\\d+\\.\\d+\\.\\d+)"
],
"datasourceTemplate": "pypi",
"depNameTemplate": "mempalace",
"autoReplaceStringTemplate": "ENV MEMPALACE_V {{newValue}}"
}
],
"packageRules": []
Expand Down