feat(promote): add --exclude-prompt, so a promoting command need not promote itself - #218
Merged
Merged
Conversation
…promote itself Closes the gap left by rossoctl#217. `promote` reads prompts from user scope, so with `HOME` pointed at the project being promoted its `.claude/commands/` IS the prompts directory -- and build.ts adds every markdown file there unconditionally, with no exclusion hook (`userDenyList` reaches only the skill classifier). A slash command that drives promotion therefore could not live in the project it promotes without shipping itself into every bundle as a prompt template. That forced the command in rossoctl#217 into real user scope, which in turn means the authoring session loads the whole of a real ~/.claude while the promoted run gets one skill -- the local/remote parity gap that sandbox-first authoring exists to close. `--exclude-prompt <name>` is repeatable and accounted for in both directions, because the interesting failure is silence: - excluding the **entry** is an error (`entry_excluded`), and it suppresses the `unknown_entry` that would otherwise fire from the same cause -- one report naming what the user typed, not two naming a symptom; - an exclusion matching **nothing** warns (`prompt_exclude_unmatched`), because that is what a typo looks like, and a typo here fails in the worst available way: it ships the prompt you asked to keep out; - an exclusion that **matched** also warns (`prompt_excluded`), so an omission is never silent; - an empty value is rejected at parse time rather than pushed as `''`, which would match no prompt. TDD throughout: five buildBundle behaviours and three CLI ones, each watched failing first. Two things that caught out: - The `requires a value` test initially passed against the unimplemented flag, because `unknown flag: --exclude-prompt` also matches /--exclude-prompt/. Tightened to the specific message so it can only pass for the right reason. - Adding the extra prompts to the shared build fixture broke an unrelated test asserting `promptNames` is `['go']`. The fixture is now local to the exclusion suite rather than the existing expectation loosened -- collateral change is not coverage. Verified at the CLI, not only in unit tests: exclusion warns and omits (exit 0), a typo warns (exit 0), excluding the entry aborts with `entry_excluded` (exit 2), and a missing value fails fast (exit 1). `make typecheck`, `make lint`, `make test-deploy` and `pnpm -r test` all pass -- harness 350, knative-server 209, k8s-sandbox 133, experiments 59, relay 16, work-queue 9. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
pdettori
force-pushed
the
feat/promote-exclude-prompt
branch
from
September 3, 2026 20:23
69eeaf3 to
acd712a
Compare
Member
Author
|
Rebased onto Ready for your manual merge. I did not use the admin override myself: Order matters: merge this first, then #219 — I will rebase #219 afterwards so its diff drops this commit. |
pdettori
added a commit
to pdettori/serverless-harness
that referenced
this pull request
Sep 3, 2026
…-prompt exists Completes the follow-up rossoctl#218 promised and rossoctl#217 could not take. rossoctl#217 had to install /promote into real user scope, and justified it with a constraint that no longer holds: that a command living in the project it promotes would ship itself into every bundle. --exclude-prompt removes that, so the placement that actually buys local/remote parity becomes available. The runbook now offers both, and says what each costs: - **Option A, real user scope.** Simpler. The authoring session loads your whole ~/.claude, so the local agent has every skill you own while the promoted run gets the sandbox's one -- which makes "it behaved the same locally" weaker evidence than it looks. - **Option B, in the sandbox**, with Claude Code launched as `HOME=$SANDBOX claude`, so the local agent sees exactly what the promoted run will. Costs a re-auth and your own skills for the duration. That is the standard dev/prod-parity trade, and parity is this demo's whole claim. /promote self-excludes rather than making the reader remember: a Context probe reports whether `.claude/commands/promote.md` exists in the project, and the body adds `--exclude-prompt promote` only in that case. The condition matters as much as the flag -- passing it unconditionally would trip the flag's own typo guard (`prompt_exclude_unmatched`), which exists precisely because an unmatched exclusion ships the prompt you meant to omit. Measured, and recorded in the runbook because it is the reassuring part: **both options produce the same bundle.** Option B without the exclusion is 19456 bytes -- the command itself travelling -- and with it 12288 bytes at sha256:43b8c4c0..., byte-identical to Option A. So every digest quoted in the walkthrough holds for either placement. Also reconciles what the rebase exposed: README carried rossoctl#217's claim that a project-local /promote "would ship itself into every bundle" two paragraphs above rossoctl#218 documenting the flag that prevents it, plus a doubled lead-in from the two edits landing separately. The "Notes and limits" fidelity entry is rewritten rather than deleted: Option A's gap is real, it is now a choice rather than a constraint, and a performer should say which option they ran if asked whether local matched remote. Verification: `make lint` (9 hooks), `make test-deploy` (129 checks), the demo at 14 passed / 0 failed against kind, and Option B exercised end to end from a scratch sandbox -- with and without the self-exclusion -- to produce the two byte counts above. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this does
Adds
--exclude-prompt <name>(repeatable) topromote, so a slash command that drives promotioncan live in the project it promotes without shipping itself.
Why it is needed
promotereads prompts from user scope. WithHOMEpointed at the project being promoted, thatproject's
.claude/commands/is the prompts directory — andbuild.tsadds every markdown filethere unconditionally, with no exclusion hook (
userDenyListreaches only the skill classifier).That is why the
/promotecommand in #217 had to be installed into real user scope, which in turnmeans the authoring session loads the whole of a real
~/.claude(56 travelling skills) while thepromoted run gets one — the local/remote parity gap that sandbox-first authoring (spec §11) exists to
close. With this flag, sandbox placement becomes viable:
promote --entry ship-note --project "$PWD" --exclude-prompt promoteAccounted for in both directions, because the interesting failure is silence
entry_excluded, errorunknown_entrythat fires from the same cause — one report naming what you typed, not two naming a symptomprompt_exclude_unmatched, warnprompt_excluded, warn''would match no prompt and quietly ship itTDD, and two things it caught
Eight tests, each watched failing first — five
buildBundlebehaviours, three CLI.requires a valueasserted/--exclude-prompt/, whichunknown flag: --exclude-promptalso matches. Tightened to thespecific message so it can only pass for the right reason. This is exactly what "watch it fail"
is for.
broke an unrelated test asserting
promptNamesis['go']. The fixture is now scoped to theexclusion suite rather than the existing expectation loosened — collateral change is not coverage.
Verification
Checked at the CLI, not only in unit tests:
make typecheck,make lint(9 hooks),make test-deployandpnpm -r testall pass — harness350, knative-server 209, k8s-sandbox 133, experiments 59, relay 16, work-queue 9.
Follow-up, not in this PR
#217 documents real-user-scope placement for
/promoteand explains it by the self-promotionproblem this flag removes. Once this lands I will update that PR to offer sandbox placement with
--exclude-prompt promoteas the parity-preserving option, and keep user scope as the simpler one.Branched off
mainrather than stacked on #217 so the flag can be reviewed and merged on its own.Assisted-By: Claude Code