2buildDocumentationGitHub
2build / DocumentationRead Markdown (.md) View source

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

VariableSet byRead byPurpose
BABYSIT_TICKETbbs 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 harnessesEvery skill preamble (via bbs ticket env's env-first override)Names the ticket id. Short-circuits all branch and cwd inference.
BABYSIT_SESSIONthe host agent's own session id (preamble default), bbs ticket session attachpreamble session-writer, bbs autopilot checkpointNames 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:

  1. $BABYSIT_TICKET (or $BBS_TICKET alias). If set, echo it. If both env vars are set and disagree → exit 2 with REASON: env conflict.
  2. manifest.yaml cwd match. Walk every tickets/<id>/manifest.yaml under the active project home (see bootstrap below). Pick the manifest whose any repos[].worktree is a parent of $PWD. Exactly one match → echo its ticket. Multiple matches → exit 2 listing them.
  3. Branch regex (the historical path): run bbs ticket env's ^(feat|fix|chore|bug|refactor|hotfix)/<ticket>_… regex over the current branch.

Exit codes:

CodeMeaning
0Resolved. Ticket id printed on stdout.
1No resolution. (Caller decides whether that's a BLOCK.)
2Explicit 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.

yaml
# 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 pushed

canonical 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

OperationWhoHow
Create on ticket creationbbs ticket ensureAtomic mktemp+mv; under existing index-lock
Update single repo's branchbbs ticket set-branch <ticket> <repo> <branch>Atomic mktemp+mv; bumps updated_at; under existing index-lock
Readbbs ticket get-manifest [<ticket>]Validates version: 1; round-trips byte-identically to a no-op set-branch
Remove oneDashboard deleteMoves one ticket directory to ~/.babysit/trash
Remove all active ticketsbbs ticket clear --allMoves 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.

yaml
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-healthz

The 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.

Code
SESSION_ID                              TICKET        AGE   CWD
7d4f1c8e-9a3b-4e2f-9b1c-8a5e6d7f2a3b    bs-abc12345   12m   .../bs-abc12345_add-healthz

bbs ticket session attach <session-id>

Prints export lines for eval. Idempotent — re-running just re-prints the same export lines (no file changes).

bash
$ 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.

FailureRECOMMENDATION line
BBS_TICKET vs BABYSIT_TICKET conflictunset one of them, e.g. unset BBS_TICKET to use the BABYSIT_TICKET value
manifest.yaml malformed YAMLedit <path> by hand, or move the malformed manifest aside to start over
manifest.yaml schema version > 1upgrade babysit (/plugin marketplace update babysit) or check out an older babysit version
BABYSIT_TICKET set, ticket dir absentunset 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-branch prints TICKET=<id>; the invoking skill stores it in context and prefixes every later bbs ticket call with BABYSIT_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 — use identity.Resolve() directly; they never need ticket inference.
  • Direct CLI — bbs ticket resolve (and --explain for 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.