Stop proxying incus one verb at a time: a rule for what claudebox owns, a table to add it, and a door out #11

Closed
opened 2026-07-13 20:39:03 +00:00 by dan-claude-bot · 0 comments
dan-claude-bot commented 2026-07-13 20:39:03 +00:00 (Migrated from github.com)

Problem

Feature requests are arriving as "wrap this incus verb" — rename this week,
incus info before it — and each one lands as a new hand-written function, a new
case arm, a new help entry, and a new synopsis string. That's a treadmill, and
it grows a CLI that is mostly a worse incus.

Two things are actually going wrong, and they want different fixes.

1. There is no rule for what belongs in claudebox. So every request is
argued from scratch, and the honest answer ("that's just incus") feels like a
brush-off even when it's correct.

2. Adding a thin command costs four edits in three places. The dispatch
case, the help text, the synopsis, the README — hand-maintained, and they
drift. That drift is not hypothetical: it is the root cause of #8 (the help
advertised status <box>, which the code ignored, and a --force that did
nothing). Any fix that leaves the surface hand-maintained will drift again.

The rule

Proposed, to live in docs/claudebox-design.md:

claudebox owns a command when it must enforce an invariant incus cannot see:
the user.claudebox=1 boundary (never touch an instance we didn't mint), the
isolation stack (claude-dev profile + claudenet + ACL), or the creds-free
snapshot→clone workflow. Everything else is incus's job — boxes are ordinary
incus instances, by design, and that door stays open.

This is a real filter, not a platitude:

  • renameours. Not because it adds logic to incus rename, but because
    resolving a box name is the logic: check the tag, apply the --remote
    prefix, and notice the box is running (incus refuses to rename a running
    instance) so we can say "stop it first" instead of leaking an incus error.
  • infoours, and mostly already built: list <box> in #9 shows state,
    type, IP and snapshot labels. Raw incus info dumps instance internals nobody
    asked for. (See the naming fix below.)
  • incus config set security.nesting=falsenot ours. It breaks the trust
    boundary; wrapping it would imply we bless it.

The mechanism: one table, not N functions

Make the thin commands data, and generate dispatch and help from the same
table — so the help can never again describe a command that doesn't exist:

# verb | incus argv | precondition | one-line help
rename | rename | stopped | Rename a box (must be stopped)
down   | stop    |         | Stop a box, keeping its state
start  | start   |         | Start a stopped box

One resolver (tag check → --remote prefix → precondition), one dispatch loop,
help rendered from the table. Adding a verb becomes a one-line data change, and
#8's whole bug class — a help text that disagrees with the code — stops being
possible by construction.

The escape hatch: so we can say no

claudebox incus <box> -- <args...>   # forwards to incus, box resolved + tag-checked

Documents what is already true — boxes are plain incus instances tagged
user.claudebox=1 — and gives the long tail a door, so "can you proxy X" gets a
one-line answer instead of a PR. It should warn, not block, when the
passthrough touches something that can break isolation (profile assign, adding
a nic, security.*): you're allowed to leave the guardrails, you just get told
you did.

Naming: list lists, info shows one

#9 currently overloads list <box> as the detail view. That's the wrong shape —
and it's what sent people to incus info in the first place. Splitting it:

  • claudebox list — all your boxes. No positional argument.
  • claudebox info <box> — one box: state, type, IP, snapshot labels, --from line.
  • claudebox list <box> — an error that points at info, not a silent surprise.

Folding this into #9 before it merges rather than aliasing over it after.

Scope

  • The rule, written into docs/claudebox-design.md.
  • list / info split (folded into #9).
  • Table-driven dispatch + generated help (on top of #10, which rewrites the
    help by hand — the table replaces that hand-maintenance).
  • rename, via the table (stop-first precondition).
  • claudebox incus <box> -- … escape hatch, with the isolation warning.
  • README: boxes are incus instances tagged user.claudebox=1; here is the door.
## Problem Feature requests are arriving as "wrap this incus verb" — `rename` this week, `incus info` before it — and each one lands as a new hand-written function, a new `case` arm, a new help entry, and a new synopsis string. That's a treadmill, and it grows a CLI that is mostly a worse `incus`. Two things are actually going wrong, and they want different fixes. **1. There is no rule for what belongs in claudebox.** So every request is argued from scratch, and the honest answer ("that's just incus") feels like a brush-off even when it's correct. **2. Adding a thin command costs four edits in three places.** The dispatch `case`, the help text, the synopsis, the README — hand-maintained, and they drift. That drift is not hypothetical: it *is* the root cause of #8 (the help advertised `status <box>`, which the code ignored, and a `--force` that did nothing). Any fix that leaves the surface hand-maintained will drift again. ## The rule Proposed, to live in `docs/claudebox-design.md`: > **claudebox owns a command when it must enforce an invariant incus cannot see:** > the `user.claudebox=1` boundary (never touch an instance we didn't mint), the > isolation stack (`claude-dev` profile + `claudenet` + ACL), or the creds-free > snapshot→clone workflow. Everything else is incus's job — boxes are ordinary > incus instances, by design, and that door stays open. This is a real filter, not a platitude: - `rename` — **ours.** Not because it adds logic to `incus rename`, but because resolving a box name *is* the logic: check the tag, apply the `--remote` prefix, and notice the box is running (incus refuses to rename a running instance) so we can say "stop it first" instead of leaking an incus error. - `info` — **ours**, and mostly already built: `list <box>` in #9 shows state, type, IP and snapshot labels. Raw `incus info` dumps instance internals nobody asked for. (See the naming fix below.) - `incus config set security.nesting=false` — **not ours.** It breaks the trust boundary; wrapping it would imply we bless it. ## The mechanism: one table, not N functions Make the thin commands *data*, and generate dispatch **and** help from the same table — so the help can never again describe a command that doesn't exist: ```sh # verb | incus argv | precondition | one-line help rename | rename | stopped | Rename a box (must be stopped) down | stop | | Stop a box, keeping its state start | start | | Start a stopped box ``` One resolver (tag check → `--remote` prefix → precondition), one dispatch loop, help rendered from the table. Adding a verb becomes a one-line data change, and #8's whole bug class — a help text that disagrees with the code — stops being possible by construction. ## The escape hatch: so we can say no ```sh claudebox incus <box> -- <args...> # forwards to incus, box resolved + tag-checked ``` Documents what is already true — boxes are plain incus instances tagged `user.claudebox=1` — and gives the long tail a door, so "can you proxy X" gets a one-line answer instead of a PR. It should **warn, not block**, when the passthrough touches something that can break isolation (`profile assign`, adding a nic, `security.*`): you're allowed to leave the guardrails, you just get told you did. ## Naming: `list` lists, `info` shows one #9 currently overloads `list <box>` as the detail view. That's the wrong shape — and it's what sent people to `incus info` in the first place. Splitting it: - `claudebox list` — all your boxes. No positional argument. - `claudebox info <box>` — one box: state, type, IP, snapshot labels, `--from` line. - `claudebox list <box>` — an error that points at `info`, not a silent surprise. Folding this into #9 before it merges rather than aliasing over it after. ## Scope - [ ] The rule, written into `docs/claudebox-design.md`. - [ ] `list` / `info` split (folded into #9). - [ ] Table-driven dispatch + generated help (on top of #10, which rewrites the help by hand — the table replaces that hand-maintenance). - [ ] `rename`, via the table (stop-first precondition). - [ ] `claudebox incus <box> -- …` escape hatch, with the isolation warning. - [ ] README: boxes are incus instances tagged `user.claudebox=1`; here is the door.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: heavy-duty/box#11
No description provided.