Ticket identity
2build's ticket identity used to live entirely on the git branch:
bbs ticket env regex-matched feat/<ticket>_<slug> at whatever cwd the
shell sat in, and "my ticket" was a function of git rev-parse --abbrev-ref HEAD. That broke anywhere the cwd wasn't a checkout of the
ticket branch (crashed sessions, per-ticket worktrees, fresh shells).
Identity is now carried by environment variables and a per-ticket
manifest.yaml, with branch derivation as a fallback inside
per-ticket worktrees.
This document is the contract. Anything that disagrees with what's here is a bug.
The env triple
| Variable | Set by | Read by | Purpose |
|---|---|---|---|
BABYSIT_TICKET | bbs ticket ensure (prints TICKET=<id>; the invoking skill stores it and prefixes later commands — exports don't survive per-call shells), bbs ticket session attach, user shell rc, test harnesses | Every skill preamble (via bbs ticket env's env-first override) | Names the ticket id. Short-circuits all branch and cwd inference. |
BABYSIT_SESSION | the host agent's own session id (preamble default), bbs ticket session attach | preamble session-writer, bbs autopilot checkpoint | Names a single agent run so concurrent / crashed sessions can rehydrate. |
Both are optional. None are required for the legacy single-repo
ergonomic path: a developer who cds into a feat-branch checkout and
runs a skill gets identical behavior to today, because env-unset falls
through to branch derivation.
Legacy BBS_TICKET alias
BBS_TICKET continues to work as an alias for BABYSIT_TICKET
(read-only, no warning) so existing shell rcs keep functioning. If both
are set and disagree, bbs ticket env exits non-zero with an explicit
"unset one of them" message — the calling preamble aborts.
Resolution order
bbs ticket resolve is the single identity entry point. Three steps,
each short-circuits:
$BABYSIT_TICKET(or$BBS_TICKETalias). If set, echo it. If both env vars are set and disagree → exit 2 withREASON: env conflict.manifest.yamlcwd match. Walk everytickets/<id>/manifest.yamlunder the active project home (see bootstrap below). Pick the manifest whose anyrepos[].worktreeis a parent of$PWD. Exactly one match → echo itsticket. Multiple matches → exit 2 listing them.- Branch regex (the historical path): run
bbs ticket env's^(feat|fix|chore|bug|refactor|hotfix)/<ticket>_…regex over the current branch.
Exit codes:
| Code | Meaning |
|---|---|
| 0 | Resolved. Ticket id printed on stdout. |
| 1 | No resolution. (Caller decides whether that's a BLOCK.) |
| 2 | Explicit conflict — env disagree, multiple manifests match cwd, schema violation. (Always BLOCK.) |
BABYSIT_PROJECT_HOME bootstrap
The manifest walk needs a project home. bbs ticket env derives the slug
from the git remote (falling back to the directory basename), and the
project home is ~/.babysit/projects/$SLUG/.
manifest.yaml schema
One file per ticket, at tickets/<ticket-id>/manifest.yaml.
Auto-generated by bbs ticket ensure; never authored by hand.
# Schema version. New files are always v1; bbs ticket resolve treats
# unknown versions as a BLOCK (loud forward-compat).
version: 1
ticket: bs-abc12345
title: Add /healthz endpoint # mirrored from index.json for human readers
created_at: 2026-05-01T11:48:59Z
updated_at: 2026-05-01T11:48:59Z
repos:
- name: <SLUG> # what bbs ticket env derives from the git remote
branch: feat/bs-abc12345_add-healthz
canonical: . # repo root
worktree: . # "." = the shared checkout (trunk tickets) — the
# resolver skips it by design, since several trunk
# tickets can share one cwd; an absolute
# .babysit/worktrees/ path when the safe-cut gate
# diverted the cut — that path participates in the
# manifest cwd-match rung
base: main
pushed: false # records whether the branch has been pushedcanonical is display-only (the dashboard reads it); identity resolution
keys off worktree. . is the special skipped value — a shared-checkout
(trunk) row is not a claim on the cwd — while absolute paths and other
relative paths (resolved against cwd) participate in the manifest
cwd-match rung. Trunk tickets therefore cannot be recovered by the
manifest rung; bare autopilot resume resolves the ticket through the
identity ladder (env → manifest → branch) and the ticket's own
checkpoint.
manifest.yaml is eagerly written by bbs ticket ensure — there is
no if file_exists branch in resolve.
Naming: manifest.yaml vs manifest.md
manifest.yaml is the identity manifest documented here.
manifest.md is the plan-draft decomposition output — an unrelated markdown
document listing sub-tickets of an oversized plan. They live in the same
ticket directory but are read by disjoint code paths. If you find
yourself confused, the one extension difference disambiguates them.
manifest.yaml writes
| Operation | Who | How |
|---|---|---|
| Create on ticket creation | bbs ticket ensure | Atomic mktemp+mv; under existing index-lock |
| Update single repo's branch | bbs ticket set-branch <ticket> <repo> <branch> | Atomic mktemp+mv; bumps updated_at; under existing index-lock |
| Read | bbs ticket get-manifest [<ticket>] | Validates version: 1; round-trips byte-identically to a no-op set-branch |
| Remove one | Dashboard delete | Moves one ticket directory to ~/.babysit/trash |
| Remove all active tickets | bbs ticket clear --all | Moves every projects/*/tickets/* directory to ~/.babysit/trash; leaves configs, sessions, analytics, branches, and worktrees untouched |
All writes acquire the existing _LOCK_PATH lock used for index.json
mutations. Concurrent set-branch calls serialize.
.babysit/sessions/<uuid>.yaml schema
One file per active Claude Code session at
~/.babysit/sessions/<session-uuid>.yaml. Auto-written by the
bbs hooks session-writer plugin hook (SessionStart + PostToolUse(Bash),
throttled to one write/60s; ticket derived from the cwd's worktree dir or
branch since hooks can't see $BABYSIT_TICKET), and by the preamble
session-writer block when a skill executes it with $BABYSIT_SESSION set;
bumped by bbs autopilot checkpoint.
version: 1
session_id: 7d4f1c8e-9a3b-4e2f-9b1c-8a5e6d7f2a3b
ticket: bs-abc12345
started_at: 2026-05-01T11:48:59Z
last_seen_at: 2026-05-01T12:14:22Z # bumped by bbs autopilot checkpoint
pid: 48291
cwd: /Users/long/workspace/lohi/babysit/.babysit/worktrees/bs-abc12345_add-healthzThe existing ~/.babysit/sessions/<PPID> touch-file (preamble line 137)
stays — its only role is the 120-min stale-session sweep, which now
incidentally covers the new <uuid>.yaml files (same dir, same mtime
sweep). No new long-running process.
Atomic writes
Session-writer and checkpoint last-seen bump MUST use atomic mktemp+mv, not in-place edit. The 120-min stale-session sweep is mtime-based; in-place edit on Linux preserves mtime, so a long-idle session whose field bumps but whose file mtime doesn't would get swept after 120 min — invisible active session. mktemp+mv creates a new inode with current mtime.
Skip when $BABYSIT_SESSION unset
The preamble hook only writes when $BABYSIT_SESSION is set. Skill
preambles run in many no-ticket contexts (direct setup or browse checks,
etc.); writing a session file for those would
pollute the session list.
Session CLI
bbs ticket session list|attach|end is the user-facing surface for
sessions.
bbs ticket session list
Reads ~/.babysit/sessions/*.yaml (skips non-yaml — the <PPID> touch
files), filters by mtime > now - 120m (matches preamble's existing
window). Output is human-readable; not designed for eval.
SESSION_ID TICKET AGE CWD
7d4f1c8e-9a3b-4e2f-9b1c-8a5e6d7f2a3b bs-abc12345 12m .../bs-abc12345_add-healthzbbs ticket session attach <session-id>
Prints export lines for eval. Idempotent — re-running just re-prints
the same export lines (no file changes).
$ bbs ticket session attach 7d4f1c8e-…
export BABYSIT_TICKET=bs-abc12345
export BABYSIT_SESSION=7d4f1c8e-9a3b-4e2f-9b1c-8a5e6d7f2a3b
$ eval "$(bbs ticket session attach 7d4f1c8e-…)"bbs ticket session end <session-id>
rm ~/.babysit/sessions/<id>.yaml. Idempotent.
Conflict semantics
Six explicit BLOCK shapes. Each emits the three-line
REASON / ATTEMPTED / RECOMMENDATION contract from
handoff-contracts.md.
| Failure | RECOMMENDATION line |
|---|---|
BBS_TICKET vs BABYSIT_TICKET conflict | unset one of them, e.g. unset BBS_TICKET to use the BABYSIT_TICKET value |
manifest.yaml malformed YAML | edit <path> by hand, or move the malformed manifest aside to start over |
manifest.yaml schema version > 1 | upgrade babysit (/plugin marketplace update babysit) or check out an older babysit version |
BABYSIT_TICKET set, ticket dir absent | unset BABYSIT_TICKET, or run bbs ticket session end <BABYSIT_SESSION>`` |
bbs ticket session attach <nonexistent uuid> | ``bbs ticket session list to see active sessions |
Env wins over disagreeing cwd
When $BABYSIT_TICKET=bs-A is set but cwd is inside ticket bs-B's
worktree, env wins (resolve step #1 short-circuits; step #2 does not
run). This is intentional — env-first is the predictable ladder. To
switch tickets, unset BABYSIT_TICKET then re-resolve.
Caller surface
The ladder (ticket.ResolveLadder — env → manifest cwd-match → branch) is
the resolver behind every ticket-inference path: bbs ticket env,
ensure, resolve, readiness, and the ticket_* handlers that derive
the ticket from the checkout resolve through resolveEnv(). This is
required because the preamble's
eval "$(bbs ticket env)" sets shell TICKET only — it neither exports
BABYSIT_TICKET nor survives per-call shells — so each command re-resolves
on its own.
- Skill init —
bbs ticket ensure --no-branchprintsTICKET=<id>; the invoking skill stores it in context and prefixes every laterbbs ticketcall withBABYSIT_TICKET=<id>(an export only holds within the same shell invocation). Downstream commands read it via the ladder's env-first rung. - Explicit-ticket commands —
get-manifest <ticket>,set-branch <ticket> …,surface compose <ticket>…,land <ticket>…,surface acquire|release --ticket,reconcile --ticket|--all— resolve project scope only (ticket.ResolveProject): an unrelated manifest ambiguity in the cwd must not reject a fully explicit command. The env-conflict abort still applies. - Project-only commands —
board— useidentity.Resolve()directly; they never need ticket inference. - Direct CLI —
bbs ticket resolve(and--explainfor debugging), user scripts.
The manifest-cwd walk is O(N tickets) and runs only when no env identity
exists — the env rung short-circuits first.
Migration
None. Greenfield. Pre-overhaul tickets that lack manifest.yaml work
unchanged via the branch-fallback path (resolve step #3). New tickets
get manifest.yaml from bbs ticket ensure. Rollback is git revert;
leftover manifest.yaml files are inert.
