Skip to content

Releases: itsvedantkumar/vstack

v1.45.1 — doctor compared 74 items and called a 75-file tree unchanged

Choose a tag to compare

@itsvedantkumar itsvedantkumar released this 23 Aug 20:35

doctor --drift printed no drift ✔ (74 item(s) compared) over a tree containing a file it
never compared.
1.45.0 shipped claude/agents/reference/ENVIRONMENT.ref through all three
install lanes and added checks for both of them, and then doctor's drift families still globbed
agents/*.md only. The reference nine agent prompts point at could be edited, truncated or left
behind by a downgrade and doctor would keep saying the installation matched the repo.

Caught by reading the count rather than the verdict: 74 items before the file shipped, 74 after.
A number that does not move when the tree grows is the whole tell, and it is only visible because
1.14.0 made doctor print the count instead of a bare no drift ✔.

It is its own family rather than another entry in the *.md loop, because the glob genuinely
differs and the reason it differs is the point: a reference named .md installs as a nameless
agent. The stale-file scan gained the same directory, so a .ref this repo stops shipping is now
reported as a leftover instead of sitting there forever.

Adding the family turned check 41 red on the way in, which is the check working. Its positive
control builds a stub tree with one member in every drift family and requires doctor to green it;
doctor grew a family, the stub did not, and the control failed over a tree that was in fact
identical. The stub now derives its members from the same list, so a future family cannot pass by
being absent from the fixture.

v1.45.0 — agent reference material, and the extension that keeps it from becoming an agent

Choose a tag to compare

@itsvedantkumar itsvedantkumar released this 23 Aug 20:29

Nine agents now carry a pointer to claude/agents/reference/ENVIRONMENT.ref, and the extension
is load-bearing.
Claude Code walks an agent directory recursively and loads every *.md at any
depth. Confirmed against the shipped binary rather than inferred:

if(d.isDirectory())return a(p,[...c,d.name]);if(d.isFile()&&d.name.toLowerCase().endsWith(".md")

marketplace.json sets "source": "./claude", so writing that reference as
claude/agents/reference/ENVIRONMENT.md would have installed it as a plugin agent called
vstack:reference:ENVIRONMENT, description auto-filled "Agent from vstack plugin". A nameless
entry in the dispatcher's list, manufactured by the audit that was looking for exactly that shape.
Check 46 holds the line in both directions: no *.md below the top level of claude/agents/,
and a planted one must be found, so the detector cannot quietly stop detecting.

The reference shipped free on the plugin lane and on no other, so install.sh, overlay.sh and
uninstall.sh now carry it too. Check 45 gained the install-lane assertion and check 46 the
overlay one, both reading the destination rather than the installer's exit code: the overlay copy
ends in 2>/dev/null || true, which is silent by construction, and row 46 points it at a decoy
directory to prove the check notices.

Contents are commands run on this machine, not recalled. Two facts this repo has been repeating in
agent briefs were wrong, and both were caught by running them:

  • grep -q under pipefail does not always return 141. printf 'a\nb\nc\n' | grep -q a
    exits 0; seq 1 2000000 | grep -q '^1$' exits 141. It fires only when the producer still has
    more than a pipe buffer to write. That conditionality is the whole trap: green on a fixture,
    141 on a corpus. The flat rule made the failure mode invisible.
  • sort -V works here. /usr/bin/sort is 2.3-Apple and orders 1.9 before 1.10. It had
    been listed with the genuine bash 3.2.57 gaps, which are mapfile and declare -A.

Cost: about 90 tokens per dispatch for the pointer, about 1,750 more only if an agent reads the
file. What it cannot do is make any agent read it, and this repo has already measured that
instructions in context do not reliably change behaviour. The claim is that a rediscovery cost
moved, not that a correctness floor rose. Falsifiable: sample the next 30 dispatches to the nine
pointed agents; if | tail, unguarded grep -q, and exit-code-instead-of-conclusion do not
fall against the same corpus's baseline, and reads of the reference stay under 20%, delete the
pointers and keep the file as human documentation.

design-reviewer and accessibility-auditor both claimed a running UI before shipping. Same
trigger, same object, and design-reviewer's phase 4 audited WCAG 2.1 AA while accessibility-auditor
audits 2.2 A+AA, so two agents reviewed the same screen to two different bars and a dispatcher had
no way to choose. This is the shape that measured both skills failing to fire rather than one
winning. WCAG conformance is now accessibility-auditor's alone; design-reviewer notes what it
cannot miss and hands off. Its missing tools: field also got a comment saying why it is missing:
omitting it is the only way the agent inherits ToolSearch and the browser tools, and a future
tightening that adds the field would remove browser access while the agent kept filing reports.

Audited by MEESEEKS M-7 (28 skills), NOOBNOOB N-2 (15 commands), JAGUAR J-1 (install lanes),
ZEEP Z-4 (14 agents), MORTY M-8 (command rewrites).

v1.44.0 — uninstall stops deleting the user's hooks

Choose a tag to compare

@itsvedantkumar itsvedantkumar released this 23 Aug 20:29

uninstall.sh deleted the user's own hook entries and reported that it had removed vstack's.
Ownership was decided by directory prefix: any entry in settings.json whose command started with
~/.claude/hooks was treated as vstack's. That directory is the conventional place for a person's
own hook scripts, and vstack installs into it rather than owning it. So a stranger who kept
personal hooks there ran uninstall.sh --yes and lost every entry pointing at them, while the
scripts themselves stayed on disk and the tool printed cleaned ... (vstack hooks, overrides and unedited policy keys removed). A destructive step reporting a narrower scope than the one it
performed, which is worse than the deletion: the operator has no reason to go looking.

The correct signal was already in this repo. install.sh derives the basenames vstack ships and
matches endswith("/hooks/" + name); the merge half had it right and the removal half never
asked. .statusLine had the same defect for the same reason. Both now match on shipped filenames.
Recoverable in the old behaviour only via $BK/pre-uninstall/, which the tool never mentioned.

Found by exercising a real install-then-uninstall under a throwaway HOME with a foreign hook
seeded first, not by reading the jq. Reading it is how it passed review the first time.

Check 45, uninstall keeps foreign settings, drops its own. Both directions against the real
scripts under a temp HOME: the user's Notification entry and their statusLine must survive,
and every hook this repo ships must be gone. A fix that removes nothing passes the user's half
trivially, which is why the second direction is not optional. Row 45 reverts the one line to the
prefix test and the check goes red naming itself.

Check 20's extractor was an allow-list wearing a scanner's clothes. It matched only
~/.claude, ~/.config/agents and ~/.conductor. /push told the model to run
~/.100xprompt/hooks/pre-push.sh, another tool's template path, in a command this repo installs.
The check written to catch exactly that could not see it, because the string did not begin with
one of three blessed prefixes. The check that exists because /bootstrap pointed at a script
nobody installs was blind to /push pointing at a script nobody installs, one namespace over.

It now reads every ~/-rooted path. A path outside the installed namespaces has to be declared in
external_path() with a reason; today that list has one entry, the repo checkout itself. Turning
it on surfaced five references across four commands. Row 20 only ever mutated inside a blessed
prefix, so it proved the half that already worked; row 20b adds the foreign-namespace lane.

Five of fifteen commands were reference documents wearing a command's frontmatter.
push.md, observability.md, deploy.md, release.md and security.md shipped in the initial
commit from a foreign template (## Usage and ## Implementation sections, tool tables, code
samples) with no imperative instruction to the assistant anywhere in the body, and were never
touched again. Between them they documented six argument interfaces (/deploy vercel,
/release patch, /security network, /security full and others) whose case statements lived
inside fenced blocks describing hypothetical standalone scripts at ~/.local/bin/deploy and
~/.local/bin/release that this repo has never installed, plus ~/nuclei-templates. All five are
now numbered instructions against tools that exist: push runs the real gate and names the
vstack trust step, deploy defers to bin/deploy-auto.sh, release defers to the
release-manager subagent it duplicated, security marks its external scanners as user-supplied.
observability.md also had a PostHog JS snippet fenced as bash, so it failed bash -n.

doctor --drift filed vstack's own logs under "presumed yours".
vstack-delegation-log.jsonl and vstack-replay-log.jsonl are written by shipped hooks and were
absent from RUNTIME_TOP, so on any machine that had actually used vstack, doctor told the
operator its own output might be a stranger's leftover. The names are now derived from the shipped
hooks rather than listed by hand. The replay log arrived in 1.43.0 and a hand-kept list would
have gone stale the same afternoon. Cosmetic: DRIFT was never set by it.

v1.43.0

Choose a tag to compare

@itsvedantkumar itsvedantkumar released this 23 Aug 19:16

1.43.0 — 2026-08-24

vstack-delegation-log.jsonl recorded only per-Stop aggregate counts, never which subagent ran
or how it went.
A session that fanned out to two dozen helpers and got a uniform 0/5 across
five fixtures could not be diagnosed afterward, because no record of the individual dispatches
survived past the transcript. claude/hooks/dispatch-counter.sh (PostToolUse, matcher
Agent|Task) now also appends one row per dispatch to
${CLAUDE_CONFIG_DIR:-$HOME/.claude}/vstack-replay-log.jsonl -- a single well-known file,
VSTACK_REPLAY_LOG overridable, findable without knowing a session id, never the same file as
the delegation log. Each row carries ts, session_id, dispatch_index (the same per-session
counter that already drives the statusline), tool_name, subagent_type, description, and,
instead of the prompt/result text itself, prompt_bytes/result_bytes (size only -- a replay
log full of verbatim prompts is a secret-leak surface, and oh-my-claudecode's own friction report
uses the same instinct to analyse context bloat without exposing prompts). duration_ms and
tool_use_id are carried too: both are already present on the real PostToolUse payload (confirmed
by reading this CLI build's own hook-input constructor, not assumed) and cost nothing extra to
record -- duration_ms is the field that actually answers "how did it go" without touching
content at all. Rotation reuses skill-mandate.sh's existing _delegation_log_row() cap (2MB,
keep last 5000 lines) rather than inventing a second policy. VSTACK_NO_REPLAY_LOG=1 disables the
replay row alone; VSTACK_NO_DISPATCH_COUNT=1 (pre-existing) disables it along with everything
else in this hook. First version of this change added the row via three more jq calls plus two
wc -c pipes per dispatch and measured 72.8ms mean / 74.9ms p95 (n=30) against this hook's own
24.1ms/27.3ms baseline -- almost entirely macOS fork+exec overhead, and well past the ~25ms p95
this hook is held to. Folded into the SAME single jq call the hook already spent on
tool_name/session_id, plus a [ -d ] guard before mkdir -p and sampling the rotation stat
check to 1 dispatch in 20: re-measured back to back against a stashed pre-change baseline on the
same machine, mean=24.5ms/p95=26.8ms against baseline mean=24.8ms/p95=25.9ms -- statistically
flat. Verified end to end against the wired hook, not a hand-made fixture: real dispatch-shaped
payloads (Task and Agent tool names, a Write tool call that must be ignored, a malformed dispatch
with no tool_input at all, and a non-ASCII description/prompt) produced exactly the rows
expected, with ~/.claude/vstack-delegation-log.jsonl (a live 92-row measurement corpus)
unchanged in line count before and after every run, and ~/.claude/vstack-replay-log.jsonl never
created by any test in this suite. One gap stated rather than left to be discovered: this hook's
matcher never sees PostToolUseFailure, which carries error instead of tool_response, so a
dispatch that failed is invisible to the replay log. The rows record what ran, not what broke.

v1.42.0

Choose a tag to compare

@itsvedantkumar itsvedantkumar released this 23 Aug 16:57

1.42.0 — 2026-08-23

The cloud sandbox gate has never armed, and has shipped inert since v1.30.0. overlay.sh
generates the sandbox setup line as bootstrap.sh | bash && vstack trust. Commit 5922ccf gave
trust a TTY confirmation prompt; a sandbox bootstrap has no terminal, so every cloud sandbox
since then has hit no terminal to confirm on; re-run with --yes to accept unseen, exited 1, and
never written the trust hash. Without that hash verify-gate.sh's Stop hook stays unarmed
permanently, which means the mechanism commit 757f3c9 was written specifically to guarantee -- a
failing .claude/verify.sh blocks the agent -- has been dead on arrival on every real sandbox for
twelve releases. The setup line now passes --yes, which is what the comment above it already
argued for: a line committed to a file, in a repo somebody deliberately dispatched work to, is the
consent, and the prompt exists for the case where nobody read it. tests/install-matrix.sh's
cloud-gate lane was calling trust without --yes and so was testing a command the setup line
does not run; it now mirrors the generated line exactly. Matrix 23 passed, 0 failed.
(SCARY-TERRY T-1.)

Nothing in this repository ever read the CI conclusion, so GitHub Actions failed on main for
six consecutive runs across two releases without anyone noticing.
The lane that catches the
defect above was correct and red the entire time. The gap was never the test: bin/doctor,
.claude/verify.sh and the release flow all inspect local same-machine state, and a falsifiable
gate still gets ignored when nothing puts its result in front of whoever is shipping. bin/doctor
now reads the conclusion field for the last run on main, never an exit code, and fails the
overall verdict on failure, cancelled or timed_out while naming the run and printing the
command to read its logs. A run still in progress carries a null conclusion and is reported as
neither pass nor failure. Every reason the check cannot run -- no gh, no auth, no network, no
remote -- prints which dependency is missing rather than a bare skip. Five-second timeout.
(PICKLE-RICK P-5.)

Two more cleanup globs disarmed by the same refactor. The mandate latch split in f4f5468
inserted a ckpt- segment into the counter filenames, and every glob anchored before that point
stopped matching. .claude/verify.sh's check 27 swept vstack-mandate-vfy-*, which matches none
of the vstack-mandate-ckpt-vfy-* files the hook now creates, and the comment directly above that
line documents it as the fix for contamination already found once -- a guard explaining why it
matters while matching nothing. Check 40 was missing the .delegate, .delegate-ts and
.delegate-scan siblings the hook hangs off the counter. Both are now anchored on both ends, and
tests/test-breadth-mandate.sh got the identical fix in 76d2366. That makes five guards one
refactor disarmed in a single day, counting the three falsifiability rows in v1.41.0, none of which
went red.

The leak turned out to be inert to check 27's assertions, and the reason it was inert is worth more
than the fix: the leaked file feeds only the delegation-log row, which that check disables. But
seeding a pre-latched counter before the first call makes all three probes return empty and the
check still passes, because case g asserts only on the third probe and pipes the first two to
/dev/null. A session that never blocked once is indistinguishable from one that hit the latch
legitimately. Recorded rather than fixed; the fix belongs with the assertion, not the cleanup.
(BIRDPERSON B-7 fixed the globs and found the weakness; NOOBNOOB N-1 fixed the sibling glob.)

v1.41.0

Choose a tag to compare

@itsvedantkumar itsvedantkumar released this 23 Aug 16:02

1.41.0 — 2026-08-23

The statusline's dispatch counter had a reader and no writer. claude/statusline.sh renders
RICK ·N▸ from ${TMPDIR:-/tmp}/vstack-dispatch-count-<session_id>, and nothing in the shipped
tree ever created that file. The segment was verified against a counter created by hand, so it
passed its own test while rendering nothing in production, for every session, forever. The reader
was written to a contract its author also specified, which is the failure: both halves were
correct against a document and neither was correct against the other. claude/hooks/dispatch-counter.sh
is the missing half. It runs on PostToolUse with matcher Agent|Task, takes the mkdir lock
already proven in skill-mandate.sh rather than a new one, never opens the transcript, and creates
the file on first dispatch rather than at session start -- absence is how a session that has not
delegated renders nothing instead of a confident ·0▸. Measured at 15.2 ms mean, 21 ms p95 over
n=30 against a real dispatch, and proven end to end rather than in halves: twenty parallel
dispatches land a counter reading exactly twenty, and the unmodified statusline renders that number
from the writer's own file. Wired into the project lane and into install.sh's user-lane rebuild,
which is the lane that also installs the statusline. Deliberately not wired into the plugin
lane: that lane ships routing and the verify gate and does not install a statusline, so a counter
there would be the same defect pointing the other way. VSTACK_NO_DISPATCH_COUNT=1 turns it off.
(POOPYBUTTHOLE P-4 built the reader and found the cost; MORTY M-5 built the writer.)

The delegation mandate went deaf for whole sessions, silently. skill-mandate.sh carried one
two-strike latch shared by every mandate family, so two early unrelated skill mandates in a session
disarmed the delegation breadth mandate for the rest of it. Measured on the session that prompted
this: all seven Stops between 12:34 and 13:38 logged latched:true with every count null, while a
forced re-scan of the same session measured dir_count=36 ext_count=61 task_count=90. The latch is
now split. The skill family keeps its two strikes per session with no re-arm. The delegation family
gets its own counter that re-arms after VSTACK_DELEGATE_RESET_SECS (default 1800), plus a re-scan
cooldown, VSTACK_DELEGATE_SCAN_COOLDOWN_SECS (default 60), so a session latched on skills and
never eligible for breadth does not pay the 1.4-2.1 s transcript scan on every Stop. That cost is
measured, not assumed: 1438 ms mean at 17.5 MB and 2009 ms at 39.1 MB, which is also why sampling
was rejected. A latched Stop now writes a row carrying latched:true and explicit nulls instead of
writing nothing, because a Stop that was never measured and a Stop that measured zero breadth are
different facts and the log could not previously tell them apart. The breadth message names up to
three of the directories it actually found. (MEESEEKS M-4, MORTY M-5.)

A per-prompt mandate line. inject-session-context.sh now prints MANDATE skill=N/2 delegate=M/2 on each prompt, read from the two counter files rather than by parsing the
transcript, and silent at 0/0. Steady state costs 305 B of the 512 B budget, worst case 470 B.
Until now the only way to discover a latched mandate was to read the log after the fact. (MORTY M-5.)

tests/delegation-drift.py crashed on the rows the fix above started writing, and reported
success while doing it.
eligible(), delegated() and detect_broken_extraction() all used
.get(key, 0), which returns the default only when a key is absent; a latched row carries an
explicit null, so each raised TypeError on live data -- 13 of 76 real rows by the time it was
found. The harness printed the traceback and exited 0. Both halves are fixed. A latched row is now
excluded from every pool by a named measured() predicate rather than silently counted as
zero-breadth or not-delegated, and the number excluded is printed unconditionally in the accounting
line, since a silent zero here is the same shape as the bug. On failure the harness now prints an
unmissable banner to stdout, which survives a caller's 2>/dev/null or | tail. Proven by
mutation against the actual pre-fix analyser. (GLOOTIE G-5.)

Three falsifiability rows had stopped mutating anything, and this release is what broke them.
Rows 23, 27 and 40 anchored their sed patterns on lines that the latch split and the destructive
guard's refactor moved or deleted, so the mutation applied cleanly, changed nothing, and the checks
they guard were green with no evidence behind them. The suite's own shasum no-op detector is what
caught it, which is the detector doing the single job it was added for. Row 23 now no-ops every
emit deny call site, including the inline duplicate the refactor created, and leaves ask and allow
alone. Row 27 makes the mandate's decision gate unconditional. Row 40 skips only the row-write
inside the latch, so the latch still exits and only the logging claim is falsified. Each was watched
red under its new anchor and restored shasum-identical. The anchors were chosen on the decision
logic each check asserts rather than on whatever line was unique that day, which is what let all
three rot through a single refactor. VSTACK_FALSIFY_ROWS=27 ./tests/gate-falsifiability.sh now
runs a subset, because a twenty-minute sweep is why nobody notices a rotted row for four
releases. That selector shipped with the defect it exists to catch: a row id with no mutation
arm mutated nothing and then reported did NOT fail when broken, which is the one sentence
this suite must never say about a check it did not test. An unknown id is now a hard error
naming the id and stating that nothing was mutated, a comma-separated value is rejected with
the accepted spelling, and the header and footer count the same rows instead of disagreeing
about whether the baseline probe is one of them.
(BIRDPERSON B-4 built check 44; BIRDPERSON B-5 found and repaired all three rows.)

The two entries below landed before the tag and are gate machinery rather than shipped
payload, which is why they carried no version bump of their own.

Closed two holes in tests/auto-trigger.sh's tool fence, both found in live transcripts rather
than by reading.
Workflow was never denied, and it is Agent's capability class — a sample
called it, and it launched in the background, wrote a generator script into ~/.claude/projects/
and fanned out to eight parallel subagents each told to edit a fixture. The denial did propagate
and nothing was touched, which is exactly the guarantee the fence's own Agent comment says
cannot be relied on: the safe outcome came from an implementation detail. Workflow is now
denied, along with Explore and Task on the same reasoning, after an audit of every tool name
in the CLI binary with a written verdict per name — the two confirmed by transcript are recorded
separately from the two denied defensively at zero cost. Second, fence_violations() compared the
file listing before and after, so an in-place Edit or a Write over an existing path left it
byte-identical and it reported clean while 32 edit attempts were made against seeded fixtures. It
now hashes fixture contents and names the modified file. The regression test was checked against
the old logic, which returns nothing on the same mutation. Writes outside the work directory stay
outside what this function sees, stated in the comment rather than left to be assumed: hashing a
peer's live working set in a shared checkout is its own hazard, and the tool denial is what
actually stood between that sample and the write. (EVIL-MORTY E-44.)

Added tests/dispatch-fleet.sh, the fleet-wide dispatch measurement over the 54 frozen
fixtures in ~/vstack-dispatch/. auto-trigger.sh measures recall per skill and nothing else;
this scores the four classes separately, because a harness that fires everything scores perfectly
on recall alone. Precision comes from the eight neg-* fixtures where the correct answer is no
skill at all, the fifteen col-* fixtures probe the overlap clusters found by reading the
descriptions rather than by measurement, and the six var-* fixtures restate the same intent
with none of the description's literal trigger words, so a library working as a keyword index
scores well on pos-* and collapses on var-*. One sample is one raw non-retrying invocation;
the retry-based case run this repo used before stops at the first success and measures "did it
ever fire" rather than a rate. Raw k/n with two-sided 95% Wilson intervals, never a bare point
estimate: at n=10 that separates "never fires" from "fires at least about half the time" and
nothing finer.

Proven against stubs with no model calls spent. Both directions of the oracle control bite — an
all-firing stub scores 75/75 recall against 0/24 precision, a never-firing stub the exact
reciprocal — and the bad-selector guard was verified by reproducing the historical
0 passed, 0 failed, exit 0 defect under mutation. Resumption is guarded: the run log header
pins model, turn budget, fence and fixture path, and a resume whose parameters disagree refuses
by naming the field rather than silently mixing two arms into one k/n. ToolSearch is logged
rather than denied, since denying it moves the measured environment away from the one real
sessions run in, and Skill can never enter the fence — a runtime guard exits 2 at startup if it
does, because denying it removes the skill listing from context entirely and the harness would
measure a fleet that is not mounted. No fleet-wide figure may be published until arm A5 of
tests/evals/build-the-lever/PREREGISTRATION.md reports, since the fence this harness inherits
may itself suppress every skill whose output is an artifact. (SUMMER S-3.)

v1.40.0

Choose a tag to compare

@itsvedantkumar itsvedantkumar released this 23 Aug 12:33

The instrument built to measure long-session delegation drift went blind in exactly the sessions
it exists to measure.
skill-mandate.sh's 2-strike latch — [ "$cnt" -ge 2 ] && exit 0, the
guard that stops a mandate the model cannot satisfy from trapping a session — sat above both the
checkpoint counter and the delegation logger. Once a session accumulated two mandate strikes every
later Stop exited before logging, permanently. Long, multi-directory sessions are the ones that
latch, and long, multi-directory sessions are the entire population tests/delegation-drift.sh
was built to describe, so the log was not merely sparse: it was filtered against its own subject.
Proven by a synthetic 3-Stop drive rather than inferred from an empty file — Stop 1 gives cnt=1,
ckpt=1, one row; Stop 2 gives cnt=2, ckpt=2, two rows; Stop 3 latches and leaves both frozen.
The checkpoint counter moved above the latch, and a latched Stop now emits
{latched:true, dir_count:null, ...} before exiting; the full-evaluation row carries
latched:false. Both paths share one _delegation_log_row(), so there is one rotation policy
instead of two copies to drift apart. Blocking behaviour is untouched. Check 40 now drives the
real hook through a synthetic multi-Stop session and asserts both directions, so this cannot
regress silently again. (MEESEEKS M-4; check 40 and row 40, BIRDPERSON B-3.)

Logging the full counts on a latched Stop was proposed and rejected on measurement, not taste.
The full evaluation path costs ~116ms on a one-line synthetic, which reads as affordable; on this
machine's real transcripts it is 1438ms mean / 1536ms p95 at 17.5MB, and 2009ms / 2114ms at
39.1MB, because the mandate pipeline scans the transcript five-plus separate times. Sampling one
latched Stop in ten would still stall the end of a long session by 1.4-2.1 seconds, on precisely
the population this latch exists to protect. The reasoning and the bar for revisiting it — a
measured p99 under 200ms against this file's own real transcripts, not another synthetic — are
recorded at the latch.

Both drift instruments counted subagent sub-transcripts as independent sessions. 965 of 3292
files under ~/.claude/projects are per-subagent leaves nested inside a parent session, and an
unbounded find admitted each as a top-level session at equal weight. Live, not theoretical: 15
of 51 replayed sessions were leaves of two parents, 13 of them from one. A session that fans out
that fanned out to thirteen leaves counted as fourteen. For delegation-drift.sh the exclusion is outright — a leaf's turn
1 is not the parent's turn 1, so it has no position in the lifetime being measured, and the parent
transcript already records the Task/Agent call that spawned it, so counting the leaf counts
one delegation twice. For compaction-effect.py the reasoning is different and is written down as
different: a compaction inside a subagent is a real event, excluded from the primary for pooling
independence rather than validity, and the excluded count now prints on its own line so a zero is
stated instead of assumed. Neither instrument's current numbers moved — contributing_sessions
held at 3 because the leaves were already failing the single-checkpoint filter, and all 8 compact
boundaries were independently confirmed top-level. The defect had not yet reached a printed
number. It would have. (GLOOTIE G-5.)

tests/compaction-effect.sh crossed from NOT EVALUATED to a result: no signal. is_error
across 3 qualifying auto boundaries reads 1/45 pre against 0/45 post (ratio 0.00x), and across 6
manual boundaries 5/90 against 7/90 (ratio 1.40x), both under a 1.5x threshold. autoCompactWindow
at 300k neither helps nor hurts the error rate at the sample available on this machine. The corpus
that unblocked it was boundary count and pooled calls, not session count. delegation-drift.sh
remains honestly NOT EVALUATED at 2 and 3 eligible windows against a floor of 8, and its secondary
block now carries the contributing-session count and no-verdict qualifier on the rate lines
themselves rather than in a header a reader can skim past. (GLOOTIE G-5.)

Added tests/plugin-manifests.sh, the by-hand authenticated-machine harness for the one lane
tests/container-matrix.sh structurally cannot measure, because a throwaway container never
installs claude. Eight checks, both positive controls biting: a validator that stops
discriminating aborts the run at rc=2 rather than reporting its silence as health, and neutered
ok/bad helpers trip the ran == 0 floor. It covers what check 19 never did — cross-referencing
claude plugin details's live component inventory against disk, every skill, command and agent
entry matched, plus SKILL.md presence and hooks.json script resolution. A skill directory with broken
frontmatter that the loader silently drops passes claude plugin validate and fails here. Also
disproved a standing assumption while building it: plugin validate and plugin details are
static and local, answering correctly under an empty unauthenticated config dir. (BETH C-3.)

Pre-registered the principle-build-the-lever investigation at
tests/evals/build-the-lever/PREREGISTRATION.md, with thresholds written before any run: confirm
at k>=8/10, falsify at k<=2/10, 3-7 reported as nothing else, whole run void if the control drops
below 7/10. Stage 0 spent 3 calls to establish that the skill description reaches the model
verbatim at MODEL=sonnet — byte-identical, 171/171 — killing the worry that
skillListingBudgetFraction was truncating the listing on the model the suite actually pins, and
establishing that the six dead hypotheses were tested against text the model really saw. Two
findings about the harness came out of the discarded probes. ToolSearch is absent from
auto-trigger.sh's --disallowedTools and this build has a deferred-tool registry: two turns
went to tool discovery returning No matching deferred tools found, which is the entire budget of
a case at the suite default of 3. That is not evidence any case has lost turns; it is evidence
nobody has looked. And Skill must never be denied in any harness — deny it and the skill listing
is not in context at all, so the harness measures a fleet that is not mounted. (ZEEP Z-3.)

v1.39.0

Choose a tag to compare

@itsvedantkumar itsvedantkumar released this 23 Aug 09:24

The headline curl-pipe installed three Claude Code plugins and edited another vendor's config
file, with no disclosure and no opt-out.
bootstrap.sh's one-liner ran setup-machine.sh,
which installed claude-mem (thedotmack/claude-mem, third-party), frontend-design
(Anthropic's own, but not vstack's to enable) and typescript-lsp
(anthropics/claude-plugins-official) unconditionally, and — whenever claude-mem was found on
disk — flipped claude-mem's own UserPromptSubmit hook from sync to async directly inside
claude-mem's hooks.json, a file this repo does not ship. Found by SCARY-TERRY's
stranger-README audit of v1.38.0. All three plugins now require --with-plugins or
VSTACK_PLUGINS=1; the default path names what it is skipping instead of silently doing it. The
hooks.json edit stays, because claude-mem ships that hook synchronous and it blocks every
prompt otherwise, but it is now gated on the plugin actually being present (idempotent
maintenance of something already opted into, not an install triggered by this run) and reversible:
the first edit leaves a hooks.json.vstack-orig sidecar that ./uninstall.sh --yes restores. A
real bug surfaced while testing this: the maintenance loop ran even under --dry-run/--check,
which both promise to touch nothing — fixed in the same commit. uninstall.sh also stopped
leaving mcpServers.cloudflare-mcp and .context7 dangling in ~/.claude.json after removing
the cloudflare-mcp wrapper (Claude Code then tried to spawn a command that no longer existed);
ownership of each entry is now decided from the install-time backup, so an entry the operator
edited since, or added themselves — like their own context7 — survives removal. (e7fd56c,
GLOOTIE G-4; README hunks in the same commit carry POOPYBUTTHOLE P-3's per-lane "what lands
where" and "confirm it worked" sections.)

/doctor reported a healthy plugin-marketplace install as broken. Both bin/doctor and
claude/commands/doctor.md assumed the full git clone + ./install.sh layout —
~/.claude/{hooks,agents,skills} — so a claude plugin marketplace add itsvedantkumar/vstack && claude plugin install vstack@vstack install, whose payload lives under
~/.claude/plugins/cache/vstack/vstack/<version>/, failed every check that assumes hooks, CLI
wrappers or a shell lane exist, which that lane never installs by design. Both now detect which
lane actually landed — preferring plugins/installed_plugins.json's recorded install path, and
falling back to the newest cache directory by mtime rather than a hardcoded version — and check
the right location, or say plainly that no install was found at all. Verified against a real
plugin-marketplace install, a real full install, and no install, in throwaway
HOME/CLAUDE_CONFIG_DIR directories. (d129d3d, POOPYBUTTHOLE P-3.)

enabledPlugins claimed typescript-lsp was on for every install, and the default install no
longer installs it.
This is the same defect class CHANGELOG.md's v1.29.0 entry fixed for
claude-mem: claude/settings.json carried "typescript-lsp@claude-plugins-official": true
unconditionally, and that was true when setup-machine.sh installed the plugin by default, but
e7fd56c (above) made it opt-in, so a default install now ships a settings file asserting a
plugin is enabled that the toolchain never installs. typescript-lsp is official
(anthropics/claude-plugins-official), not third-party like claude-mem, but that difference is
immaterial to this defect — the failure mode is "settings claims enablement, install path does
not deliver it" either way, not a supply-chain question. claude/settings.json's enabledPlugins
is now {}, asserting no plugin is enabled by default, matching what the default install
actually does for all three plugins. No hard dependency on the key's prior value was found —
bin/doctor's enabledPlugins reference checks the key exists at all (for the overlay's
project-key gate), not what it names; tests/install-matrix.sh's overlay-preserves case uses a
fictitious theirs@x plugin name, not typescript-lsp; claude/settings.project-keys lists
enabledPlugins among the keys deliberately never overlaid into another repo, unaffected by its
value.

Fixing this exposed a second, related defect in install.sh's settings merge, present since the
claude-mem fix and reproduced for typescript-lsp the moment its key was added there too:
del(.enabledPlugins["claude-mem@thedotmack"]?) ran unconditionally on every ./install.sh, so a
user who opted in with --with-plugins/VSTACK_PLUGINS=1 — and therefore has the plugin for
real — had their own explicit choice silently undone on the next reinstall, because install.sh
cannot see a flag passed to setup-machine.sh in an earlier, separate run. e7fd56c's commit
body named this and left it for install.sh to decide. Fixed here rather than left standing:
install.sh now runs claude plugin list — the same presence check setup-machine.sh already
uses — before the merge, and only strips a plugin's enabledPlugins entry when it is not actually
installed; an entry backed by a real install survives every future reinstall. Verified in a
throwaway HOME both ways: a live install with the plugin present keeps its entry across a
reinstall, and a stale claim with nothing backing it (no plugin cache, as on any fresh
--with-plugins-less machine) is stripped, in both cases leaving an unrelated foreign
enabledPlugins key untouched.

v1.38.0

Choose a tag to compare

@itsvedantkumar itsvedantkumar released this 23 Aug 08:47

principle-type-system-discipline almost never fired. Rewriting its description around the
nouns a user actually types moved it from 1/10 to 9/10 at n=10 — matching the control's rate.
The same rewrite method applied to principle-build-the-lever did not move it.

The shipped description read "Apply when designing types or a function signature in TypeScript,
Rust, Go, or other statically-typed code" — mechanism vocabulary, not language a prompt actually
contains. Rewritten to name the concrete shapes instead: "Apply when a struct, enum, or type can
hold an invalid combination of fields that shouldn't compile," dispatched at 9/10, matching the
control. principle-build-the-lever got the identical treatment — surfacing its own literal
nouns in place of mechanism language — and it did not move: 2/10, sitting exactly on the
pre-registered falsification floor, and the rewrite was reverted rather than shipped. A method
that fixes one skill's dispatch rate and fails to fix another is a real finding about the limits
of the description-rewrite lever, not a pattern to repeat blind — record both halves or the
failure gets silently forgotten the next time someone reaches for the same trick.

skill-mandate.sh now logs delegation counts per Stop, for measuring the breadth mandate's
effect on delegation behaviour going forward instead of only its blocks.

The Stop hook already computes dir_count/ext_count/task_count/named to decide whether to
block; this appends one line per evaluated Stop to a JSONL log (session_id,
checkpoint_index, the four counts, ts). Counts only — no paths, no file contents, matching
the discipline the mandates already apply before anything reaches a block message. Logs
unconditionally, blocks conditionally, so the log reflects the rate the mandate is trying to
move rather than only the cases it already caught. Opt-out VSTACK_NO_DELEGATION_LOG=1, same
shape as the existing VSTACK_NO_MANDATE escape hatch; VSTACK_DELEGATION_LOG overrides the
destination. Capped at ~2MB with tail-and-atomic-mv rotation to the last 5000 lines, checked
with one O(1) stat per Stop rather than a line count. Measured latency: 28.3ms with the logger
active vs 28.6ms without, over 60 samples at σ≈1.9ms — indistinguishable from zero.
tests/delegation-drift.sh (+ delegation-drift.py) is the accompanying analyser: pre-registered
thresholds, states its own reverse-causality confound, and reports NOT EVALUATED rather than a
rate below its eligible-window floor — the correct, expected result on day one, confirmed
against this machine's own data.

v1.37.0

Choose a tag to compare

@itsvedantkumar itsvedantkumar released this 23 Aug 07:27

The delegation and agent-naming mandates counted a tool name that does not exist in this
build.

skill-mandate.sh's task_count matched only tool_use blocks named Task, the classic Claude
Code CLI's dispatch-tool name. This build's SDK calls the same tool Agent. Measured against a
real 15MB transcript: 70 Agent dispatches, 0 Task matches. Two mandates read that count: the
breadth/delegation mandate reported "zero subagents" over a session that ran 70 of them, and the
agent-naming mandate -- gated on that same count being >= 1 -- was structurally unable to ever
fire and had never fired in any install since it shipped, despite call-sign naming being a
specific, standing request from the repo owner. The enforcement was inert and silent, and silence
reads like compliance. Fixed by counting Task or Agent. TaskCreate was checked and
deliberately excluded: its .input shape is {subject, description, activeForm}, a checklist
item, not a dispatch call.

Second defect in the same hook: the Bash-write path extractor added in v1.36.0 was reading
write-shaped lines out of heredoc bodies as if this session had performed them, and treating
any $VAR-containing redirect target as a real file. A cat > /tmp/check35.sh <<'CHK' ... CHK
block whose fixture body happened to contain $g_empty/app/src/C.tsx was reported as TypeScript
someone had written. On the same transcript: dir_count 53 -> 34, ext_count 83 -> 60, extracted
paths 345 -> 241, and the breadth mandate goes from firing on phantom writes to completely silent
on a session that made one real edit. emit() now drops any candidate containing an unexpanded
$, and a heredoc body is suppressed only once its opening line already matched a write rule --
so cat >file <<EOF (body is inert file content) is suppressed but python3 - <<PY (body is
executed and its real open(..., 'w') call is a genuine write) still counts. Latency cost: +74ms
(+13%) on the same transcript.

Also added: prove-it-works, a Stop-hook check on the assistant's own closing claim.
principle-prove-it-works scored 0/10 on its own fixture prompt because its trigger -- "apply
before declaring any task or fix done" -- is a condition on the assistant's forthcoming speech
act, not on anything in the user's prompt a skill matcher can score against. The equivalent check
now runs directly in the Stop hook, at the moment that condition is actually about: a turn that
edits a file and closes with a completion claim ("done", "it works now", "all tests pass", and
similar bounded phrasings), with zero Bash/Read/Task/Agent tool_use anywhere in the turn to back
it up, blocks naming prove-it-works. Deliberately generous in the silent direction -- any Bash
or Read call counts as evidence regardless of what it did or when in the turn it ran, relative to
the edit -- because a mandate that produces a false block teaches users to disable the whole gate.

Falsification: tests/test-breadth-mandate.sh PROOFs 7-9 cover prove-it-works (blocks on zero
evidence; silent after a real Bash call; silent on a purely conversational claim with no edit).
PROOFs 10-12 cover the two dispatch-hook fixes (Agent dispatch suppresses the breadth mandate the
same as Task; zero Task AND zero Agent still trips it; a $VAR-containing Bash write target is
not counted). All 12 proofs pass; the pre-fix hook was falsified by hand against the same
fixtures and restored byte-identical.