Files
felhom.eu/documentation/runbooks/workspace-CLAUDE.md
T
admin c21bcf84f7
gates / gates (push) Successful in 7s
docs+gate: instruction files cannot silently regrow (R-229)
New shared scripts/instructions_gate.py, registered in controller_gates.py and
agent_gates.py, never copied into a sibling repo (the reuse_refs_check.py
precedent). 20 fixture tests, all asserting the effect: exit code AND that the
message names the file and the reason.

It is a consistency gate, not a budget gate, and the failure message says so. A
/context reading measured the instruction files at 15k tokens against 869k free in
a 1M window -- space is not the constraint, and a future reader must not re-derive
the wrong reason. The 200-line ceiling is adherence guidance; a file nobody can
hold in their head is where contradictions hide, and five were found here.

Checks run against effective text (HTML comments stripped, because they are
stripped before injection): the line ceiling; every .claude/rules/*.md declares
paths: or an explicit unconditional: true; no component version literal; no
TEMPORARY block carrying a past date; and the workspace-root CLAUDE.md is
byte-identical to its versioned copy -- the live file sits outside any git repo,
so that copy is its only version-controlled record.

Two traps recorded so they are not reintroduced: a bare \d+\.\d+\.\d+ matches the
first three octets of every IPv4 (the gate excludes dotted quads, or it fails on
192.168.0.180 in the agent's own file); and unconditional: true is NOT a Claude
Code feature but this project's own marker.

Workspace-root CLAUDE.md 208 -> 182 lines (142 effective), copy kept identical.
The nine-instance invariant table moved into the felhom-testing skill, which
triggers when writing or reviewing a test; all three directive bullets stayed in
the core. felhom.eu/CLAUDE.md got surgical corrections only and is knowingly still
over the ceiling at 227 effective lines -- closing it needs the restructure R-229
defers, said plainly rather than quietly absorbed.

CONTEXT.md gains standing ruling S-35. OPEN-ITEMS.md gains R-229.

Docs only -- no Go, no version bump, nothing built or deployed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JJc8sAGRWmavP3rMtdpkr2
2026-08-06 09:38:52 +02:00

11 KiB

CLAUDE.md — /mnt/5_hdd/felhom.eu/git workspace root (DooPlex)

What this workspace is

A parent folder holding the felhom sibling repos. Most are one logical product — Felhom, a managed home-server service for Hungarian households. (Any non-felhom repo is unrelated; ignore unless asked.)

Claude Code runs HERE, on DooPlex (192.168.0.180), as kisfenyo. Builds are local; the Proxmox host is one SSH hop. Run CC inside tmux so sessions survive SSH drops: tmux new -A -s cc.

  • Hub — operator backend on k3s (hub.felhom.eu), in felhom.eu/hub/.
  • Host agent — one per Proxmox host, operator-tier, owns all Proxmox interaction: felhom-agent/.
  • In-guest controller — one per customer LXC, Docker-only: felhom-controller/.
  • Also: app-catalog-felhom.eu/ (app templates), homelab-manifests/ (DooPlex k3s).

Each repo's own CLAUDE.md and .claude/rules/ load when you touch files there. The four Felhom skills are installed from felhom.eu/skills/ with python3 felhom.eu/scripts/install_skills.py (symlink — repo edits are live immediately).

This host is production infrastructure

DooPlex runs Gitea, the container registry, k3s + Longhorn, PBS, and the hub. Treat it accordingly:

  • NEVER run docker system prune, docker image prune -a, or any global Docker cleanup here.
  • NEVER touch k3s data dirs, Longhorn mounts, PBS datastores, or Gitea storage. Workspace is /mnt/5_hdd/felhom.eu/ — stay inside it plus ~/build symlinks/dirs.
  • Destructive disk/guest operations belong to felhom-pve via the agent — never on this host.
  • Do not run Claude Code with permission prompts disabled on this host.
  • Watch disk headroom before large builds: df -h /mnt/5_hdd / — abort if either is >90%.

Artifact taxonomy (it prevents the "what do I do?" stall)

The planning/architecture assistant (in claude.ai, "project Claude") produces files with distinct roles. A file being open in the editor is NOT an instruction. If no task is stated, ask.

  • TASK.md / TASK-*.md — a spec for you (Claude Code) to implement. Implement it when it is placed as TASK.md at a repo root, or when explicitly told "implement ". Then push, update CHANGELOG.md, and write the repo's REPORT.md.
  • RUNBOOK-*.md — an operational procedure. CC executes every step it has access and capability for, live hosts included (CC has root@felhom-pve SSH + the felhom-agent token). Mark a step HUMAN only when it genuinely needs physical presence, a real-world decision, or credentials CC lacks. Do not decline a whole procedure because it touches a live host or a privileged token. Confirm before irreversible ops on real customer data; demo scratch guests are fair game.
  • Validation/review — checking a push against a spec's criteria is project Claude's job, not yours, unless asked.

Standing rules — each earned by a real failure (R-96)

  1. Never combine a test run and a commit in one command. A combined command has ONE exit code and the interesting one gets swallowed. Run the suite, read rc, then commit.
  2. A "no access" claim must list what was tried. "No access" is unfalsifiable unless it names its attempts.
  3. An absent log line is not evidence of correct behaviour. Verify with a POSITIVE observable — something that MUST appear when the system is healthy. An empty log is equally consistent with "working" and "stopped entirely".
  4. A recommendation that is not followed gets one line saying why. Silence reads as agreement and the disagreement is lost.

Shared conventions

  • Push to main directly — no feature branches.
  • Versioning via build-time ldflags (-X main.version/-X main.Version); bump on meaningful changes + a CHANGELOG entry.
  • Code quality: double-check for bugs/edge cases; add debug logging; ask rather than guess when you'd otherwise need to invent input or output.

Clean-tree gate before any build: git status --porcelain must be empty and git rev-parse HEAD must equal git rev-parse origin/main in the repo being built. An unpushed change does not exist — never build a dirty or unpushed tree. The git pull in the build step stays (it is a no-op when you work in this tree, and load-bearing if anything was pushed from elsewhere).

In every repository where you make a change, update both files in that repo:

  • CHANGELOG.md — a cumulative log of all changes; newest entry on top.
  • REPORT.mdoverwrite with a summary of the most recent implementation (or significant validation/operational run) only; not cumulative.

Never write secrets — tokens, passwords, private keys, API keys — into CHANGELOG.md, REPORT.md, or any committed file. Reference them as "stored out-of-band" instead.

Live validation — no browser here

claude-in-chrome is NOT available on DooPlex. The standard method is endpoint-level: invoke the exact endpoint the UI invokes (no server logic is skipped, only rendering) and say which method was used. Strict end-to-end UI coverage is a manual click-through by the operator.

Access

Local (this host): repos /mnt/5_hdd/felhom.eu/git/<repo>, build dirs /mnt/5_hdd/felhom.eu/build/felhom-{controller,hub,agent}, sudo kubectl, Go toolchain, Docker build+push to gitea.dooplex.hu/admin/.

Host addresses, routes, break-glass and per-node facts: felhom.eu/documentation/operations/nodes.md — the single home. Do not restate them elsewhere.

Which box do I break?felhom.eu/documentation/runbooks/target-selection.md — the tiers, and per machine what is freely permitted / needs care / forbidden, each with its reason. Read it before picking a machine for a drill, a destructive test or a throwaway VM. A task that needs a victim names one; an absent fence is not permission. DooPlex is Tier 2 — precious: it is the recovery chain, and never a drill target.

Component versions are not recorded in any inventory doc — agent/controller/hub versions change several times a day and the fleet is not uniform. Ask the hub's /hosts + /configs, or felhom-agent --version / pct exec <vmid> -- docker ps on the box.

Memory

Project memory lives at /mnt/5_hdd/felhom.eu/git/.claude-memory/, surfaced via ~/.claude/projects/-mnt-5-hdd-felhom-eu-git/memory (symlink); MEMORY.md is the index. Memories reflect what was true when written — verify a named file/flag still exists before acting on it.

Presence is not success

A timestamp recording an attempt must never be read as evidence of a result. Where a status field travels alongside a timestamp, the verdict consults both — or the timestamp records only successes. Ask of any timestamp: what exactly must have happened for this to be set? If the answer is "we tried", it cannot answer "did it work".

Corollary: when a verdict changes which field it counts from, the alarm text has to change with it. Leaving the message reading last run 8h ago while alarming on a six-day-old success turns a true alarm into one the operator dismisses.

A comment asserting an invariant needs a test pinning it, or it is a wish

Nine instances in this project have shipped guarantees the code did not provide — each survived review because the comment read as settled, and three were caught only on live hardware. The case table is in the felhom-testing skill, which loads when you write or review a test, harden a guard, or fix a bug.

  • If a comment states an invariant, name the test that pins it, or write one.
  • If an invariant has a stated dependency ("if either invariant changes, revisit this"), that is not a safeguard — nobody revisits. Pin it with a test that fails when the dependency moves.
  • Prefer a test that asserts the consequence (does the alarm fire?) over one that asserts the mechanism (does suppression expire?). R-97b's Scenario F proved the mechanism and the consequence was still broken.