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
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,10 +183,8 @@ troubleshooting — is in **[`deploy/knative/README-ocp.md`](deploy/knative/READ

### Promoting a local Claude Code workflow

Iterate on a workflow locally in Claude Code — skills, `CLAUDE.md`, memory, a slash command —
then promote it:

From inside Claude Code, in the project you want to promote:
Iterate on a workflow locally in Claude Code — skills, `CLAUDE.md`, memory, a slash command — then
promote it from inside Claude Code, in the project you want to promote:

```bash
mkdir -p ~/.claude/commands # once
Expand All @@ -198,9 +196,13 @@ export SH_HARNESS_DIR=/path/to/serverless-harness # once, per shell

`/promote` sets the `HOME` override and `--project` for you, checks that the repo boundary and the
Redis tunnel are right before uploading, and reads the digest back through the cluster's own client
afterwards. Install it into your **real** `~/.claude/commands/`: with `HOME` pointed at the project,
`promote` bundles every prompt in that project's `.claude/commands/`, so a `/promote` living there
would ship itself into every bundle.
afterwards.

Installed in your **real** `~/.claude/commands/` as above, it is visible in every project and never
travels. You can instead keep it **in the project**, which lets you run Claude Code with
`HOME=<project>` so the local agent sees exactly what the promoted run will; `/promote` then detects
that it lives there and self-excludes with `--exclude-prompt promote` (see below), because otherwise
it would ship itself into every bundle.

Or drive the CLI directly:

Expand Down
10 changes: 9 additions & 1 deletion deploy/claude/commands/promote.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ allowed-tools notes, so a later edit does not widen this back:
* pnpm stays broad on purpose. The documented invocation begins with env assignments
(`HOME="$PWD" REDIS_URL=... pnpm --dir ...`), so a narrower `Bash(pnpm --dir:*)` prefix is not
reliably matched, and the env has to be inline because exports do not survive between tool calls.
* echo is granted because five of the six Context probes end in `&& echo`/`|| echo`.
* echo is granted because the Context probes end in `&& echo`/`|| echo`.
* the self-exclusion probe needs nothing new: `test -f` and `echo` are already granted.
* jq and `git rev-parse` were granted but never invoked, and are gone. `git init` stays: guard 1
offers it.
-->
Expand All @@ -25,6 +26,7 @@ allowed-tools notes, so a later edit does not widen this back:
- Workflow config here: !`ls -d .claude/skills .claude/commands 2>/dev/null || echo "no .claude/skills or .claude/commands"`
- Repo boundary for the context walk: !`test -e .git && echo ".git present" || echo "NO .git — see guard 1"`
- Cluster Redis tunnel on 16379: !`lsof -nP -iTCP:16379 -sTCP:LISTEN >/dev/null 2>&1 && echo "listening" || echo "absent"`
- This command lives in the project: !`test -f .claude/commands/promote.md && echo "yes — must self-exclude" || echo "no"`

## Your task

Expand Down Expand Up @@ -62,6 +64,12 @@ HOME="$PWD" REDIS_URL=redis://localhost:16379 \
pnpm --dir "$SH_HARNESS_DIR/harness" promote --entry <entry> --project "$PWD" <extra-args>
```

**If the Context above says this command lives in the project, add
`--exclude-prompt promote`.** Otherwise it ships itself into the bundle as a prompt template, since
`HOME="$PWD"` makes this project's `.claude/commands/` the prompts directory. Add it **only** in that
case: passing it when there is no local `promote.md` produces a `prompt_exclude_unmatched` warning,
which is the flag's own typo guard firing on a false alarm.

Two parts of that are load-bearing, so do not "simplify" them:

- **`HOME="$PWD"`** makes this directory its own user scope, so only this workflow travels. Without
Expand Down
14 changes: 14 additions & 0 deletions deploy/claude/tests/promote-command.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,20 @@ check "runs the CLI via --dir on the harness checkout" \
check "explains why --project is not redundant" \
"$(grep -ciE 'do not "simplify"|not "simplify" them' "$CMD")" "1"

echo "== it self-excludes when it lives in the project it promotes"
# Without this the command ships itself as a prompt template whenever it is installed in the project
# (which is the placement that buys local/remote parity). The conditional matters as much as the
# flag: passing --exclude-prompt unconditionally would trip the flag's own typo guard.
check "probes whether the project carries this command" \
"$(grep -c 'test -f .claude/commands/promote.md' "$CMD")" "1"
check "tells the model to pass --exclude-prompt promote" \
"$(grep -c -- '--exclude-prompt promote' "$CMD")" "1"
# Folded to one line before matching: `grep` is line-based and this phrase wraps in promote.md, so
# an `only.*in that case` pattern never matched and the check passed on the warning name alone --
# leaving the conditionality it is named for unpinned against a reflow that dropped the "only".
check "says to add it ONLY when the command is local" \
"$(tr '\n' ' ' < "$CMD" | grep -ciE 'add it [^.]*only[^.]*in that case' | awk '$1>0{print 1; exit} {print 0}')" "1"

echo "== the redis tunnel is the private port, not 6379"
check "port-forwards 16379:6379" "$(grep -c '16379:6379' "$CMD")" "1"
check "REDIS_URL uses 16379" "$(grep -c 'redis://localhost:16379' "$CMD")" "1"
Expand Down
60 changes: 53 additions & 7 deletions docs/demos/promoted-workflow-demo.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,19 +84,49 @@ export SH_HARNESS_DIR=$(pwd)

### Install the `/promote` command

Act 2 runs promotion from **inside** Claude Code. Install the command once:
Act 2 runs promotion from **inside** Claude Code, and there are two places to install it. They differ
in one thing that matters for this demo: whether the session you author in resembles the one that
runs remotely.

**Option A — real user scope. Simpler; use this if you are just following along.**

```bash
mkdir -p ~/.claude/commands
cp deploy/claude/commands/promote.md ~/.claude/commands/promote.md
```

> **It belongs in your real `~/.claude/commands/`, not in the sandbox.** With `HOME` pointed at the
> sandbox, `promote` reads the sandbox's `.claude/commands/` as its prompts directory and bundles
> every markdown file there unconditionally — there is no exclusion flag, since the deny-list only
> covers skills. A `/promote` living in the sandbox would therefore ship itself into every bundle as
> a prompt template. In your real user scope, Claude Code sees it in every project and `promote`
> never does.
Claude Code sees `/promote` in every project, and `promote` — reading the sandbox as user scope —
never sees it, so it cannot travel. The cost: your authoring session also loads your real
`~/.claude`, so locally the agent has every skill you own while the promoted run gets the sandbox's
one.

**Option B — in the sandbox, for actual parity.** Put the command in the sandbox, so the local agent
can be made to see _exactly_ what the promoted run will:

```bash
mkdir -p $SH_DEMO_SANDBOX/.claude/commands
cp deploy/claude/commands/promote.md $SH_DEMO_SANDBOX/.claude/commands/promote.md
```

Installing is all that happens here. The matching launch (`HOME=$SH_DEMO_SANDBOX claude`) belongs in
**Act 1b**, and only after Act 1a has populated the sandbox: run it now and Claude Code would start
on an empty sandbox with no tunnels open, and your shell would be left in `$SH_DEMO_SANDBOX`, where
Act 1a's repo-relative `cp` paths cannot resolve.

> **This is what `--exclude-prompt` is for.** With `HOME` pointed at the sandbox, that
> `.claude/commands/` _is_ promote's prompts directory, and every markdown file in it travels — so
> without the flag the command would ship itself into every bundle as a prompt template. `/promote`
> detects that it lives in the project and adds `--exclude-prompt promote` itself; you do not pass
> it by hand. It adds the flag **only** in that case, because an exclusion matching nothing warns —
> the flag's own typo guard.
>
> The trade: a fresh `HOME` means Claude Code re-authenticates, and you lose your own skills for the
> duration. That is the standard dev/prod-parity cost, and parity is the whole claim this demo makes,
> so Option B is the more honest way to perform it.
>
> **Either option produces the same bundle**, so every digest quoted below holds for both. Measured:

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

suggestion — This is true of the digests but not of the invocation quoted in Act 2a (line 260):

HOME="$PWD" REDIS_URL=redis://localhost:16379 \
  pnpm --dir "$SH_HARNESS_DIR/harness" promote --entry ship-note --project "$PWD"

That block is introduced as "the command it runs" and sits immediately below the 12288 bytes / sha256:43b8c4c0… output — but under Option B what /promote actually runs carries --exclude-prompt promote, and the quoted form as written produces 19456 bytes. That is the very discrepancy this PR measured.

Since the block exists to be read aloud, an Option B performer would be narrating a command that neither matches what just ran nor yields the digest on screen. A parenthetical at line 260 would close it — e.g. (plus --exclude-prompt promote under Option B) — and would make the "holds for both" claim here fully literal.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 2a18d63 — and this was the sharpest of the three, because the PR reproduced in its own narration the exact discrepancy it measured. The block now reads:

HOME="$PWD" REDIS_URL=redis://localhost:16379 \
  pnpm --dir "$SH_HARNESS_DIR/harness" promote --entry ship-note --project "$PWD"
#   ...plus --exclude-prompt promote under Option B, which /promote adds for you

plus a line saying the flag is not cosmetic: without it the bundle is 19456 bytes rather than the 12288 printed above. So "either option produces the same bundle" is now literally true of the command on screen, not only of the digests.

> Option B without the exclusion is 19456 bytes (the command itself travelling); with it, 12288 bytes
> and `sha256:43b8c4c0…` — byte-identical to Option A.

### Open the two tunnels

Expand Down Expand Up @@ -184,6 +214,13 @@ It records that the regression is tracked as **KAG-4471** and ships behind
cd $SH_DEMO_SANDBOX && claude
```

Under **Option B**, launch it this way instead — a sandbox `HOME` is what makes the local agent's
configuration identical to the promoted run's:

```bash
cd $SH_DEMO_SANDBOX && HOME=$SH_DEMO_SANDBOX claude
```

Ask it: `Write the ship note for the auth timeout fix.` You get the house `SHIP NOTE` block with
the ticket and the risk line. This is the "works on my laptop" baseline — the thing that normally
does not survive the trip.
Expand Down Expand Up @@ -232,8 +269,12 @@ dispatch with: {"sessionId":"<run>/<item>","kind":"prompt","prompt":"…","conf
> ```bash
> HOME="$PWD" REDIS_URL=redis://localhost:16379 \
> pnpm --dir "$SH_HARNESS_DIR/harness" promote --entry ship-note --project "$PWD"
> # ...plus --exclude-prompt promote under Option B, which /promote adds for you
> ```
>
> That trailing flag is not cosmetic: under Option B the command is in the sandbox, so without it the
> bundle is 19456 bytes rather than the 12288 printed above.
>
> `--project "$PWD"` looks redundant and is not: without it the CLI promotes the directory the
> process started in, which through `pnpm --dir` is the harness checkout. Measured — it reports
> `project: …/serverless-harness/harness`.
Expand Down Expand Up @@ -456,6 +497,11 @@ kubectl exec -n $NS deploy/redis -- redis-cli DEL "config:bundle:$DIGEST"

## Notes and limits

- **Option A's authoring session is not what runs remotely.** With your real `HOME`, Claude Code
loads your whole `~/.claude` while the promoted run gets the sandbox's one skill — so "it behaved
the same locally" is weaker evidence than it looks. Option B closes that with
`HOME=$SH_DEMO_SANDBOX` plus `--exclude-prompt`, at the cost of a re-auth. Say which one you ran
if someone asks whether local matched remote.
- **The `TOKEN:` line is the one model-dependent claim.** The format and the token both require the
model to choose to `read` the skill body. `CLAUDE.md` says it MUST, and the deterministic channel
carries that instruction, so it is reliable in practice — but it is not a mechanical guarantee the
Expand Down
Loading