Skip to content

v0.0.15 - #1

Merged
IvanRublev merged 47 commits into
masterfrom
v0.0.15
Jul 31, 2026
Merged

v0.0.15#1
IvanRublev merged 47 commits into
masterfrom
v0.0.15

Conversation

@IvanRublev

@IvanRublev IvanRublev commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Version 0.0.15

What changed

  • Each agent in the fleet gets () · in prompt box, /resume picker, terminal title. Nickname is short word no live peer holds — "kill kit" instead of reading eight hex chars. Names survive context restarts.
  • Fleet-wide TOTAL block + per-instance token stats — every exit path sums todos, todo time, tokens across all instances (crashed peer's landed work still counts), with input/output/cache breakdown per instance.
  • Double-claim bug fixed — launching inside leftover task worktree now refused; previously two instances could claim same todo and land merges into peer's in-flight branch.

Tests run

Scenario Verdict Evidence
S1 shellcheck (claudezero.sh) PASS clean
S2 shellcheck (emitted scripts) PASS compact-exit-hook.sh, zero.sh, checkbox-merge.sh all clean
S3 bash 3.2 parse PASS parses under /bin/bash 3.2
A parallel zeroing + restart + timing PASS see table below
B startup-guard refusals PASS B1–B5 all refused correctly, no *-task-*-task-* branch
C1 merge_task abort contract PASS winner=0, loser=2, base clean, MERGE_HEAD absent, branch/worktree kept, CB unchecked
C2 real-claude race, self-heal PASS (on rerun) see note below
C3 real-claude STOP compliance PASS never landed, branch/worktree kept, stop reported, real engagement=yes
D foreign check-off refusal PASS phantom check=none, 2/2 landed, git clean, 0 leftover branches
E1 in-flight credit PASS credited A=40s, idempotent, owner kept A, sync claim/merge=0, sync done=1
E2 startup GC PASS dead file/count/marker + nomark file/count all gone
F fenced-checkbox immunity PASS dojo_proud=1, real done id=0, fenced ids=1/1
G1 token dedupe/accumulation PASS report1=1.7k, report2=2.7k, per-instance file namespaced
G2 token degradation PASS Tokens: n/a both modes, timing rows kept
K1 fd 4 fallback PASS stub output kept, own output kept
K2 fd 4 split (manual/pty) not run scripted-out by design, needs real terminal
H1–H5 session naming PASS unsplit argv, restart-stable, unique nicks, suffix overflow, dead-peer name reclaim, unreadable-registry fallback all correct
I claim exit paths PASS I1–I7 all match want (0/1/4/3/0/4, usage=1)
J1 fleet TOTAL PASS instances=2, todos=2m30s·3 completed, tokens=1.2k, categories correct
J2 solo/degrade TOTAL PASS singular heading, 0-file→no block, garbled→n/a, loop mode→0 todo rows

Ivan Rublev and others added 30 commits July 30, 2026 23:39
The root guard used `git rev-parse --show-toplevel`, which inside a leftover
`../ts-*` task worktree returns that worktree's root, so the guard passed and
BASE_BRANCH became a peer's claim branch — shifting the per-task lock name and
letting two instances claim the same todo, with merges landing in the peer's
in-flight branch instead of the base.

Replace the check with the first entry of `git worktree list --porcelain`,
which is always the main worktree, so one compare refuses both the subdir
launch and the linked-worktree launch. `--show-toplevel` is removed, not kept
as a fallback.

- TEST.md: loosen B3's message assertion, add B4 (leftover branch + worktree,
  launch inside it, assert non-zero exit and no `*-task-*-task-*` branch).
- .github/smoke.sh: numbered case 7 for the porcelain|sed main-root form,
  asserted from both the main and a linked worktree (BSD vs GNU sed).
Increment a per-instance todos-done-<base>-<id> counter at the same merge_task
rc=0 point that credits ownership time, so the count and the time credit always
agree on who did the work. Report it on the Todos: line and rename the heading
to execution stats. Delete the Claude loops accumulator (CLAUDE_TOTAL /
WAS_DONE / RUN_START and its per-iteration all_todos_done call).

Closes ISSUE-015
…udeZero (ISSUE-018)

`claudezero.sh 2>&1 | tee run.log` captured every TUI redraw and escape
sequence. claude now writes to fd 4, which points at the terminal when stdout
has been redirected and a controlling terminal exists, and falls back to a dup
of stdout otherwise (no redirect, or no tty as in tests/CI/nohup) — so captured
output is unchanged there.

`usage()` gains the logging example with the Ctrl+C-safe `{ trap '' INT; tee
run.log; }` wrapper, plus a comment recording why the braces are load-bearing:
Ctrl+C signals the whole foreground group and a bare pipe reader dies, taking
the closing report with it.

No in-script logging mode: the shell already redirects; this only stops claude
from polluting the redirect.

TEST.md gains Scenario G — G1 asserts the no-tty fallback deterministically
(session-leader detach via setsid or a python3 shim, since macOS has no
setsid), G2 documents the pty check with both BSD and util-linux `script`
syntaxes.
# Conflicts:
#	claudezero.sh
…vity>

Pass --name to the claude launch so the prompt box, /resume picker and terminal
title carry the same instance id the execution-stats report heads, plus a dojo
student activity picked from the id (16#<first two chars> % 10) so the name is
stable across context restarts. Loop mode gets it too. TEST.md Scenario G covers
name construction, unsplit argv, restart stability and the derivation.

Closes ISSUE-019
Sum the four billed token categories (input, output, cache creation, cache
read) from the claude session transcripts, and print them as a headline total
with the breakdown beneath the timing rows.

The Stop hook now records each session's transcript_path into a per-instance
list in the git dir (destination passed via CLAUDEZERO_TRANSCRIPTS, since the
hook file itself is shared by all instances in the repo). After claude exits,
the loop makes one whole-file awk pass over those transcripts, deduping by
requestId — a single API request is written as one line per content block,
each repeating the same usage object — and taking only the parent field on
each line, never the usage.iterations[] copy or the cache_creation ephemeral
leaves that already sum into it.

No dollar figure: there is no first-party programmatic rate source, and a
hardcoded table would print confidently wrong money after any model launch.
Any parse miss prints "Tokens: n/a" and leaves the run untouched.

Also renames the report heading from "execution time" to "execution stats" —
a token block is not a duration.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
# Conflicts:
#	.github/smoke.sh
#	README.md
#	TEST.md
#	claudezero.sh
…SSUE-020)

The zero prompt spent three agent steps on one decision — "is this task mine
to work?" — and a failed validation in step 2.c left the claim from 2.b behind
until a peer stole it.

`claim_task` composes the three functions that already sit side by side in the
emitted zero.sh: acquire, validate the worktree's branch, then `is_done`. It
prints the worktree path on stdout (single field, no newline) and exits 0 when
the task is yours; each skip reason goes to stderr with its own code — 1 not
claimed, 3 validation failed, 4 already landed — which the prompt treats
alike. On exit 3 it clears the session marker but leaves the worktree alone:
force-removing a worktree on an unexpected branch would destroy unknown work.

`acquire`, `release` and `done` stay as subcommands (TEST.md drives them
directly). No new lock, no new fd — acquire's per-task reclaim lock already
spans the whole call.

Prompt steps b/c/d collapse into one CLAIM step; e/f/g become c/d/e, and the
step-letter references in TEST.md were recomputed against the final prompt
(the merge references said 2.f while the prompt already had 2.g). TASK_BRANCH
stops being an agent variable — failure reports now read the branch with
`git -C "$wt" symbolic-ref --short HEAD`.

TEST.md gains Scenario H covering all four exit paths, the single-field
stdout, the exit-3 claim leak, and the raw-vs-sanitized id split. `claim` goes
through ensure_owner, so the driver runs under a copy of bash named `claude`.
# Conflicts:
#	TEST.md
Every exit path (Ctrl+C break, MAX_LOOPS break) now prints this instance's
execution-stats report after credit_inflight_time, then a TOTAL block summing
todos, todo time and tokens across every instance of this run. The sum is a glob
over the per-instance files in the git common dir — no registry, no flock, no
baseline — so a crashed peer's landed work still counts. Solo runs print no
TOTAL. TEST.md Scenario I covers the sums, dedupe, solo suppression and
degradation.

Closes ISSUE-017
Names the claude session `(<id>) <nick> · <activity>`: a 3-5 letter word no
peer running against this repo currently holds, so an operator can say "kill
kit" instead of reading eight hex chars off a terminal title.

The nickname lives on line 3 of the existing $INSTANCE_DIR/<id> liveness
marker, so it is released on exit for free and a crashed instance's name comes
back at the next launch's GC — no registry file, no reservation TTL. The pick
runs once per launch, after the startup GC and under instance.lock, so two
instances launched together cannot draw the same word. Past fifteen live
instances names take an ascending suffix ("bob 1", then "bob 2").

An unreadable registry or a failed lock costs uniqueness, never the launch.
ClaudeZero's own output is unchanged — reports still key on the id.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
print_fleet_total suppressed the block whenever the base-slug globs yielded a
single instance id, on the grounds that a one-instance total only restates the
per-instance block above it. That holds for the figures but not for the heading,
which names the instance count — the one figure the per-instance block never
carries. Suppressing it made a correct solo sum indistinguishable from a fleet
sum that matched no files because the base slug was not what the operator
thought.

Lower the guard to -ge 1 and pluralize the heading from the same printf, so the
block reads identically at every fleet size. n=0 stays silent: absence now has
one meaning.

TEST.md J2 fabricates this run's own transcript so exactly one id lands on disk,
asserts the singular heading and matching figures, and adds a zero-ids case
proving the guard is not -ge 0. ISSUE-017's superseded solo-suppression spans
point at this bug.
Indices 5, 6, and 9 of dojo_student each claimed a single unit of work,
so a name like `(2A56168E) moss · carving one checkbox into ice` asserted
a ceiling the instance does not have — it keeps claiming todos until the
list is empty. Reword in place at their existing indices; the modulus and
the id → index mapping are untouched, so names stay restart-stable.

Indices 5 and 6 also stop sharing the checkbox-carving image, so the ten
slots are ten distinct labels again.

TEST.md's ACT array, ISSUE-019's list and rationale, and ISSUE-021's
example name follow the new strings.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
VERSION and a dated CHANGELOG section for the 27 commits on this branch:
fleet-wide TOTAL, per-instance tokens and todo counts, session names and
nicknames, claude's TUI on fd 4, the collapsed `claim` step, and three fixes
(linked-worktree launch guard, solo TOTAL, dojo wording).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The tap bump fired on `release: published`, so cutting a version needed a
GitHub Release in addition to the tag. Homebrew only needs the tarball at
`archive/refs/tags/vX.Y.Z.tar.gz`, which exists as soon as the tag does, and a
Release adds nothing the CHANGELOG does not already carry.

A tag push has no release payload, so name the tag and commit explicitly via
`github.ref_name` and `github.sha`.

Not automated: creating the Release from a workflow. A Release created with
GITHUB_TOKEN does not trigger other workflows, so a chained tap bump would
silently never fire.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
"permission auto mode" reads as a mode named "permission auto"; the flag's
own wording is auto permission mode. "zero todos faster" repeats the tool's
own jargon where plain "work out todos" carries it.
Ivan Rublev and others added 17 commits July 31, 2026 12:28
Zero mode reads the todo through `git show <base>:<path>`, so the list has to
live in the repo whose branches carry the work. It sat under the now-ignored
`issues/`, where every lookup resolved to nothing.

Drops the per-line ticket links: the ticket files stay in the issues repo,
which a task worktree cannot see.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… branch

The merge gate diffs the todo against the branch's fork point on the base.
A todo that is not tracked there — never added, or gitignored, which the
dirty-tree guard does not catch — makes every merge refuse for checking
zero boxes, so no task can ever land. Refuse at startup instead.

Covered by TEST.md scenario B5.
The execution-stats header printed only the eight hex characters of the
instance id, while the terminal title and /resume picker carry the
nickname. A report in a scrollback could not be matched to the terminal
it came from without reading hex.

The nickname is now drawn into INSTANCE_NICK at launch (still one
pick_nickname call) and the header reads
`execution stats (instance <id> · <nick>)`.

ISSUE-027
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
bash 3.2 — the /bin/bash macOS ships, and what a Homebrew install runs —
cannot parse a heredoc inside $(…). `prompt=$(cat <<'PROMPT_EOF' … )` made
the whole script unparsable there: "unexpected EOF while looking for
matching `''", so the macOS CI leg was red and no mac user could run it.

Read the heredoc straight into the variable with `read -r -d ''` instead,
trimming the trailing newline `$(cat)` used to strip. Rendered prompt is
byte-identical to before, under both bash 3.2 and bash 5.

Covered by TEST.md scenario S3.
A descriptor opened from the /dev/tty clone device is not kqueue-registrable
on macOS, so a piped run killed claude at startup with `EINVAL … kqueue`.
Dup the terminal stdin the shell was handed instead: a real tty fd, opened
read-write, so it takes writes and kqueue both. The probe becomes
`[ ! -t 1 ] && [ -t 0 ]`.

Scenario G drops the setsid/python3 detach dance for a plain `< /dev/null`,
and G2 no longer needs a script(1) wrapper — the documented pipe form from a
terminal is exactly the case now.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…s in the readme

The changelog's 0.0.15 section missed BUG-026 — the guard that refuses a
zero-mode launch when the todo file is not tracked on the base branch.

The readme still showed the pre-branch UI: version 0.0.14 in the sample run, no
fleet TOTAL block, and no mention of named claude sessions or the fd-4 split
that makes a piped run log only ClaudeZero's reports. Quickstart now also says
the todo file has to be committed on the branch.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds missing articles (notably in the todo-format example line), splits
comma splices, fixes the dangling participle in the header, and adds the
missing colon before the test command.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@IvanRublev
IvanRublev merged commit 6f6226e into master Jul 31, 2026
3 checks passed
@IvanRublev
IvanRublev deleted the v0.0.15 branch July 31, 2026 21:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant