Landing the Plane Skill
Overview
The "Landing the Plane" workflow ensures that all work is properly committed, tested, synced, and pushed to remote before ending a session. This skill automates the complete session shutdown process with mandatory quality gates.
The critical principle: every failure is a signal. A failing test is a design smell. A linter warning is a code smell. A dangling branch is a process smell. Either fix it now or file it as a bead — nothing gets swept under the rug.
When to Use This Skill
Invoke when:
- Ending a work session: "Land the plane before ending"
- Before going AFK: "Complete and push all work"
- After implementing features: "Finish up and sync"
- Before switching contexts: "Land current work and prepare handoff"
Core Principles
1. Main Must Be Clean
Main is the source of truth. It must build, pass all tests, have zero warnings, and zero lint violations at all times. If main is dirty, nothing else matters until it's clean.
2. Every Failure Is a Smell
A failing test is not "something to fix later." It's a design or code smell that demands either:
- Fix it now — resolve the issue before landing
- File it as a bead — create a tracked issue with full context so it gets fixed properly
There is no third option. "I'll remember to fix it" is not acceptable.
3. Nothing Local
All completed work MUST be on remote. Local-only work is lost work.
4. No Orphans
No open branches without purpose. No worktrees left dangling. No zjj sessions abandoned. No stale stashes hiding work. Everything is either merged, filed as a bead, or explicitly abandoned.
5. Clean Handoff
Next session (or teammate) should know exactly where things stand.
6. Exponential Backoff on Merge & Push Failures
Any operation that can fail transiently (merge conflict after rebase, push rejection, network blip) MUST use exponential backoff. Tight retry loops against a merge queue or remote are how sessions end up in a terrible state — hammering a conflict that won't self-resolve, or spinning forever on a network blip.
Backoff Schedule (shared by merge and push):
| Attempt | Wait Before Retry | Cumulative |
|---|---|---|
| 1 | — (immediate first try) | 0s |
| 2 | 2s | 2s |
| 3 | 4s | 6s |
| 4 | 8s | 14s |
| 5 | 16s | 30s |
| 6 | 32s | 62s |
| 7 | 60s (cap) | 122s |
- Max retries: 6 (7 total attempts). After exhaustion, STOP. Do not keep trying.
- Cap: 60s per wait. Never wait longer than 60s between attempts.
- Before each retry: re-sync first (
zjj syncorjj git fetch). The conflict or rejection may have been caused by stale state. Re-syncing is cheap and often resolves the issue. - On exhaustion: run
zjj recover --diagnosefirst (surfaces orphans, stale locks, DB issues). Then file a bead, preserve workspace state (do NOTzjj abort), flag the user. The workspace is evidence.
Pseudocode — merge path (uses zjj retry):
# Safety snapshot BEFORE the loop
zjj checkpoint create
WAIT=2
MAX_RETRIES=6
RETRY=0
EXIT_CODE=3
while [ $RETRY -lt $MAX_RETRIES ] && [ $EXIT_CODE -ne 0 ]; do
# Re-sync before retry — stale state is the #1 cause of repeated failures
zjj sync
[ $? -ne 0 ] && break # sync conflict needs manual resolution — stop loop
# zjj retry retries the last failed command (the done)
zjj retry
EXIT_CODE=$?
[ $EXIT_CODE -eq 0 ] && break
RETRY=$((RETRY + 1))
echo "Attempt $((RETRY + 1))/$((MAX_RETRIES + 1)) — waiting ${WAIT}s before retry..."
sleep $WAIT
WAIT=$((WAIT * 2))
[ $WAIT -gt 60 ] && WAIT=60
done
if [ $EXIT_CODE -ne 0 ]; then
echo "EXHAUSTED: merge retries failed."
zjj recover --diagnose # see what's broken
echo "Filing bead. Workspace preserved."
# Rollback to checkpoint if state is corrupt:
# zjj rollback --to <checkpoint-id> <session-name>
fi
Pseudocode — push path (uses jj git fetch + re-push):
WAIT=2
MAX_RETRIES=6
RETRY=0
EXIT_CODE=1
while [ $RETRY -lt $MAX_RETRIES ] && [ $EXIT_CODE -ne 0 ]; do
jj git fetch # re-fetch — non-fast-forward often self-resolves
jj git push --bookmark main
EXIT_CODE=$?
[ $EXIT_CODE -eq 0 ] && break
RETRY=$((RETRY + 1))
echo "Push attempt $((RETRY + 1))/$((MAX_RETRIES + 1)) — waiting ${WAIT}s..."
sleep $WAIT
WAIT=$((WAIT * 2))
[ $WAIT -gt 60 ] && WAIT=60
done
if [ $EXIT_CODE -ne 0 ]; then
echo "EXHAUSTED: push retries failed. Flag user — permission or branch protection."
fi
Landing Workflow (10 Mandatory Steps)
Step 1: Audit Open Branches, Worktrees, and Sessions
Objective: Identify everything that's open. Nothing hides.
Actions:
# === ZJJ WORKSPACES (CHECK FIRST) ===
# List all zjj workspaces (isolated work sessions)
zjj list 2>/dev/null
zjj status 2>/dev/null # Current workspace details
# Show workspace age and activity
zjj list --verbose 2>/dev/null
# For detailed status of each workspace:
for ws in $(zjj list 2>/dev/null | awk '{print $1}'); do
echo "=== Workspace: $ws ==="
zjj status -n "$ws" 2>/dev/null
done
# === JJ WORKSPACES (if using jj directly) ===
jj workspace list 2>/dev/null
# === GIT BRANCHES ===
# List all local branches
git branch
# List branches NOT merged into main
git branch --no-merged main
# List remote branches
git branch -r
# === GIT WORKTREES ===
git worktree list
# Check for worktrees not tracked by zjj (orphans)
git worktree list | grep -v "$(git rev-parse --show-toplevel)" | grep -v "zjj-"
# === STASHES ===
git stash list
For EACH open item, decide:
Open zjj workspace?
├─ Work is COMPLETE → zjj done -m "..." (Step 4 - merges to main)
├─ Work is INCOMPLETE but VALUABLE → File bead (Step 2), then decide:
│ ├─ Continuing next session → Keep workspace, document in handoff
│ └─ Not continuing → zjj abort -w <name> (proper discard, updates bead)
├─ Work is ABANDONED/STALE → zjj abort -w <name>
└─ Work is IN-PROGRESS → Document state in bead, keep workspace
Open branch/worktree (not zjj-managed)?
├─ Work is COMPLETE → Merge to main (Step 4)
├─ Work is INCOMPLETE but VALUABLE → File bead (Step 2), then decide:
│ ├─ Can merge partial work safely → Merge to main
│ └─ Cannot merge safely → Abandon with bead reference
├─ Work is ABANDONED/STALE → Remove it:
│ ├─ git branch -d <branch>
│ └─ git worktree remove <path>
└─ Work is IN-PROGRESS → Document state in bead, keep open (EXCEPTION)
Open stash?
├─ Recent work → git stash pop, commit properly
├─ Valuable but not applying → File bead with context, git stash drop
└─ Stale/forgotten → Review, file bead if valuable, git stash drop
FLAG THE USER if:
- Any zjj workspace has been open > 7 days without commits
- Any branch has been open > 7 days without commits (and not zjj-managed)
- Any worktree exists that isn't tracked by zjj (orphan worktree)
- Any zjj workspace is in
failedorstalestate - Any stash is > 3 days old
- More than 3 branches/workspaces are open simultaneously
PRIORITY ORDER FOR AUDIT:
- zjj workspaces - Most important, these are your active isolation units
- git branches - Might be legacy work not yet moved to zjj
- git worktrees - Should be managed by zjj, flag if orphaned
- stashes - Danger zone, often forgotten work
Required Outputs:
- All zjj workspaces inventoried with status (complete/in-progress/abandon)
- All git branches inventoried with merge decision
- All worktrees accounted for (zjj-managed or orphaned)
- All stashes reviewed and decided
- No orphans remain without a decision
- User flagged for any anomalies
Step 2: File Beads for Remaining Work
Objective: Every incomplete item gets a tracked bead using br create. Nothing lives only in memory. Never use GitHub Issues — beads are the single source of truth.
Actions:
# Search for TODOs and FIXMEs in code
grep -r "TODO\|FIXME\|HACK\|XXX\|WARN" src/ --exclude-dir=node_modules --exclude-dir=target 2>/dev/null
# Review current state
git status
git diff
git diff --staged
# Create beads (see Bead Template below for description format)
br create "[smell-type] Brief description" \
--type task \
--priority 2 \
--description "$(cat <<'EOF'
<full bead description — see template below>
EOF
)" \
--acceptance "$(cat <<'EOF'
<acceptance criteria — see template below>
EOF
)" \
--labels "smell:code,severity:important"
What gets a bead:
- Any TODO/FIXME/HACK/XXX in code you touched this session
- Any failing test you couldn't fix in this session
- Any linter warning you couldn't resolve
- Any compiler warning
- Any incomplete branch that's being abandoned
- Any design concern surfaced during work
- Any technical debt deliberately taken on
Quick capture (for rapid filing during landing — enrich later):
BEAD_ID=$(br q "[code] Fix clippy warning in auth module" --type task --priority 2 --labels "smell:code")
br update "$BEAD_ID" --description "..." --acceptance "..."
Bead Description Template (EARS + BDD)
Every bead MUST follow this structured format. Use br create with --description and --acceptance flags.
Description (--description):
## Context
What was being done when this surfaced.
Current state: what files, branches, or partial work exist.
## Smell Classification
- **Type**: design | code | test | process | debt
- **Severity**: blocking | important | minor
- **Gate Failed**: test | lint | format | build | type | warning | N/A
## Dependencies
- **Blocks**: [bead IDs this blocks, if any]
- **Blocked By**: [bead IDs that must resolve first, if any]
- **Related**: [bead IDs with relates-to relationship]
## Requirements (EARS)
### Invariants (Ubiquitous — always true, no keyword)
The <system> shall <behavior that must always hold>.
Example: "The CLI shall exit with non-zero status on any error."
### State-Driven (While)
While <precondition>, the <system> shall <expected behavior>.
Example: "While the workspace is active, zjj status shall show the session."
### Event-Driven (When)
When <trigger event>, the <system> shall <expected response>.
Example: "When the user runs `br lint`, all open beads shall be checked for missing sections."
### Optional Feature (Where)
Where <feature is present>, the <system> shall <behavior>.
Example: "Where --json flag is provided, output shall be valid JSON."
### Unwanted Behavior (If/Then)
If <unwanted condition>, then the <system> shall <recovery behavior>.
Example: "If the database is locked, then br shall retry with backoff up to 30s."
## Variants
- **Happy Path**: The expected normal-use scenario
- **Alternate Paths**: Valid alternative flows
- **Error Paths**: Expected failure modes and recovery
## Design Notes
Any architectural context, trade-offs, or constraints.
Acceptance Criteria (--acceptance):
## High-Level Acceptance Criteria
1. [Criterion 1 — what must be true for this bead to be closed]
2. [Criterion 2]
3. [Criterion 3]
## Acceptance Tests (BDD — Outer Layer)
### Scenario: <Happy path scenario name>
Given <precondition>
When <action>
Then <expected outcome>
And <additional verification>
### Scenario: <Error path scenario name>
Given <precondition>
When <error-triggering action>
Then <expected error handling>
And <state remains clean>
### Scenario: <Edge case scenario name>
Given <boundary condition>
When <action at the boundary>
Then <expected behavior at the edge>
## Verification
- [ ] All acceptance scenarios pass
- [ ] No new warnings introduced
- [ ] `br lint` passes on this bead
After creating, wire dependencies:
# If this bead blocks another
br dep $BEAD_ID --blocks $OTHER_ID
# If this bead is blocked by another
br dep add $BEAD_ID $BLOCKER_ID
# If beads are related
br dep relate $BEAD_ID $RELATED_ID
After creating, validate:
# Lint the bead to ensure required sections are present
br lint $BEAD_ID
Bead Examples
Example 1: Failing test (code smell)
br create "[test] Auth module password validation test fails on empty string" \
--type bug \
--priority 1 \
--labels "smell:test,severity:important,gate:test" \
--description "$(cat <<'EOF'
## Context
Surfaced during landing quality gates. Test `test_validate_password_empty`
fails with assertion error. Pre-existing failure, not introduced this session.
File: tests/auth_test.gleam:47
## Smell Classification
- **Type**: test
- **Severity**: important
- **Gate Failed**: test
## Dependencies
- **Blocks**: none
- **Blocked By**: none
## Requirements (EARS)
### Event-Driven (When)
When the user provides an empty password, the auth module shall return
Error(EmptyPassword) with a user-facing message.
### Unwanted Behavior (If/Then)
If the password validation receives a null/empty input, then the system
shall reject it without panicking or returning Ok.
## Variants
- **Happy Path**: Non-empty password passes validation
- **Error Path**: Empty string returns Error(EmptyPassword)
- **Edge Case**: Whitespace-only string treated as empty
EOF
)" \
--acceptance "$(cat <<'EOF'
## High-Level Acceptance Criteria
1. Empty password returns Error(EmptyPassword)
2. Whitespace-only password returns Error(EmptyPassword)
3. Valid password passes validation
## Acceptance Tests (BDD)
### Scenario: Empty password rejected
Given a user registration form
When the user submits an empty password
Then the system returns Error(EmptyPassword)
And the error message says "Password cannot be empty"
### Scenario: Whitespace-only password rejected
Given a user registration form
When the user submits " " as password
Then the system returns Error(EmptyPassword)
### Scenario: Valid password accepted
Given a user registration form
When the user submits "correcthorsebatterystaple"
Then the system returns Ok(ValidatedPassword)
## Verification
- [ ] All three scenarios pass in test suite
- [ ] No new warnings introduced
- [ ] `br lint` passes
EOF
)"
Example 2: Linter warning (code smell)
br create "[code] Clippy warns about unnecessary clone in db module" \
--type task \
--priority 3 \
--labels "smell:code,severity:minor,gate:lint" \
--description "$(cat <<'EOF'
## Context
cargo clippy -- -D warnings fails on src/db/connection.rs:142.
Warning: unnecessary `.clone()` on a value that implements Copy.
## Smell Classification
- **Type**: code
- **Severity**: minor
- **Gate Failed**: lint
## Requirements (EARS)
### Invariant
The codebase shall compile with zero clippy warnings under -D warnings.
### Event-Driven (When)
When `cargo clippy -- -D warnings` is run, the build shall succeed with
exit code 0.
## Variants
- **Happy Path**: Remove .clone(), use Copy semantics
- **Alternate**: If Clone is intentional for future-proofing, add #[allow(clippy::clone_on_copy)] with comment
EOF
)" \
--acceptance "$(cat <<'EOF'
## High-Level Acceptance Criteria
1. `cargo clippy -- -D warnings` passes with zero warnings
2. No behavior change from removing the clone
## Acceptance Tests (BDD)
### Scenario: Clippy passes clean
Given the codebase at HEAD
When `cargo clippy -- -D warnings` is run
Then exit code is 0
And stdout contains no "warning:" lines
## Verification
- [ ] clippy clean
- [ ] all tests still pass
EOF
)"
Example 3: Orphan branch (process smell)
br create "[process] Stale branch 'feature-cache' open 12 days without commits" \
--type task \
--priority 3 \
--labels "smell:process,severity:minor" \
--description "$(cat <<'EOF'
## Context
Discovered during landing orphan audit. Branch `feature-cache` was last
committed to 12 days ago. Contains partial caching layer implementation.
3 files changed, 142 insertions.
## Smell Classification
- **Type**: process
- **Severity**: minor
- **Gate Failed**: N/A (orphan audit)
## Requirements (EARS)
### Event-Driven (When)
When the caching feature is prioritized, the work on branch `feature-cache`
shall be resumed, rebased onto main, and completed.
### Unwanted Behavior (If/Then)
If the caching feature is deprioritized, then the branch shall be deleted
and this bead updated with the design notes for future reference.
## Variants
- **Happy Path**: Resume work, complete feature, merge to main
- **Alternate**: Cherry-pick useful parts, abandon the rest
- **Abandon**: Delete branch, preserve design notes in this bead
EOF
)" \
--acceptance "$(cat <<'EOF'
## High-Level Acceptance Criteria
1. Branch is either merged to main or deleted
2. No orphan branch remains without a tracked bead
## Acceptance Tests (BDD)
### Scenario: Branch merged
Given branch `feature-cache` exists
When the caching feature is completed
Then the branch is merged to main
And the branch is deleted locally and remotely
And this bead is closed
### Scenario: Branch abandoned
Given branch `feature-cache` is deprioritized
When the decision to abandon is made
Then the branch is deleted
And design notes are preserved in this bead
And this bead is closed with reason "abandoned — design notes preserved"
## Verification
- [ ] `git branch --no-merged main` does not show `feature-cache`
- [ ] Bead closed with clear reason
EOF
)"
Required Outputs:
- All TODOs/FIXMEs catalogued
- Beads created using
br createwith full EARS + BDD template - Each bead has smell type, severity, and labels
- Dependencies wired with
br dep -
br lintpasses on all new beads - No untracked work left in memory only
Step 3: Run Quality Gates (ZERO TOLERANCE)
Objective: Main must be clean. Every failure is a smell that gets either fixed or filed.
Gate 1: Tests
# Detect project type and run tests
make test 2>/dev/null \
|| cargo test 2>/dev/null \
|| gleam test 2>/dev/null \
|| npm test 2>/dev/null \
|| pytest 2>/dev/null \
|| go test ./... 2>/dev/null
Gate 2: Linting (ZERO warnings)
# Rust
cargo clippy -- -D warnings 2>/dev/null
# Gleam
gleam check 2>/dev/null
# JavaScript/TypeScript
npm run lint 2>/dev/null
# Python
ruff check . 2>/dev/null
# Go
golangci-lint run 2>/dev/null
Gate 3: Formatting
cargo fmt --check 2>/dev/null \
|| gleam format --check src/ test/ 2>/dev/null \
|| npm run format:check 2>/dev/null \
|| ruff format --check . 2>/dev/null \
|| gofmt -l . 2>/dev/null
Gate 4: Build (ZERO warnings)
# Rust — deny warnings at build level
RUSTFLAGS="-D warnings" cargo build 2>/dev/null \
|| cargo build 2>/dev/null
# Other
gleam build 2>/dev/null \
|| npm run build 2>/dev/null \
|| make build 2>/dev/null \
|| go build ./... 2>/dev/null
Gate 5: Type Checking
npm run typecheck 2>/dev/null \
|| tsc --noEmit 2>/dev/null \
|| mypy . 2>/dev/null
Gate 6: Compiler/Runtime Warnings
# Capture and review any warnings from build output
# Rust: cargo build 2>&1 | grep "warning:"
# Gleam: gleam build 2>&1 | grep "Warning"
# TypeScript: tsc 2>&1 | grep "warning"
FAILURE PROTOCOL — Every failure is a smell:
Quality gate failed?
├─ Can I fix it RIGHT NOW (< 5 minutes, obvious fix)?
│ ├─ YES → Fix it. Re-run ALL gates. Commit the fix.
│ └─ NO → File a bead with full context:
│ ├─ Smell Type: test | lint | format | build | type | warning
│ ├─ Gate: which gate failed
│ ├─ Output: exact error output
│ ├─ Severity: blocking | important | minor
│ └─ Context: what caused this, what was being changed
│
├─ Is this a PRE-EXISTING failure (not caused by this session)?
│ ├─ YES → File bead, note "pre-existing", continue landing
│ └─ NO → This session introduced it. MUST fix before landing.
│
└─ Is this BLOCKING the build/tests entirely?
├─ YES → CANNOT land. Fix it or revert the breaking change.
└─ NO → File bead, continue landing with warning in handoff report.
CRITICAL RULES:
- ZERO test failures on main — if tests fail, fix or revert
- ZERO linter warnings —
-D warningsis the standard - ZERO compiler warnings — warnings are future bugs
- ZERO format violations — run the formatter, commit the result
- New failures introduced this session MUST be fixed, not filed
- Pre-existing failures get filed as beads but don't block landing
Required Outputs:
- All tests passing (exit code 0)
- Zero linting violations
- Zero compiler/runtime warnings
- Code properly formatted
- Build succeeds
- No type errors
- Any failures that couldn't be fixed have beads filed
Step 4: Merge to Main (Merge Queue)
Objective: All completed work lands on main. Branches close. zjj acts as merge queue.
ZJJ MERGE QUEUE WORKFLOW (PREFERRED):
zjj done command acts as a merge queue - it syncs workspace changes to main, runs quality gates, and cleans up the workspace automatically.
# === ZJJ MERGE QUEUE (ONE COMMAND) ===
# From within the zjj workspace
zjj done -m "Brief description of completed work"
# This does:
# 1. Syncs workspace commits to main branch
# 2. Merges into main (rebase or merge based on config)
# 3. Pushes to remote
# 4. Removes the workspace
# 5. Cleans up the Zellij session
# If you want to review before merge:
zjj sync # Sync changes without merging
zjj diff main # Review what will be merged
git log main..HEAD # See commits to be merged
zjj done -m "..." # Proceed with merge
If zjj done exits with code 3 (merge conflict) — use exponential backoff:
See the Exponential Backoff Protocol in Core Principles above for the full schedule (max 6 retries, 2→4→8→16→32→60s waits, capped at 60s). Use zjj retry — it retries the exact last failed command. Preview first with zjj whatif done.
# 0. Preview before attempting (more detail than --dry-run)
zjj whatif done
# 1. Safety checkpoint BEFORE the retry loop
zjj checkpoint create
# 2. Backoff retry loop
WAIT=2
MAX_RETRIES=6
RETRY=0
EXIT_CODE=3
while [ $RETRY -lt $MAX_RETRIES ] && [ $EXIT_CODE -ne 0 ]; do
# Re-sync FIRST — main may have changed, rebase may clear the conflict
zjj sync
if [ $? -ne 0 ]; then
echo "Conflict during sync. Resolve manually in workspace, then retry."
break # hand off to user for manual resolution
fi
# zjj retry retries the last failed command (the done)
zjj retry
EXIT_CODE=$?
[ $EXIT_CODE -eq 0 ] && break
RETRY=$((RETRY + 1))
echo "Merge attempt $((RETRY + 1))/$((MAX_RETRIES + 1)) — waiting ${WAIT}s..."
sleep $WAIT
WAIT=$((WAIT * 2))
[ $WAIT -gt 60 ] && WAIT=60
done
# 3. On exhaustion: diagnose, preserve, flag
if [ $EXIT_CODE -ne 0 ]; then
zjj recover --diagnose # surfaces orphans, stale locks, DB issues
echo "EXHAUSTED: merge retries failed. Workspace preserved. Filing bead."
# DO NOT zjj abort. The workspace is evidence.
# File a bead (see Step 2), document exact conflict output.
# If state is corrupt, rollback: zjj rollback --to <cp-id> <session>
# Flag the user — this needs manual intervention.
fi
TRADITIONAL GIT/JJ WORKFLOW (if not using zjj):
# === GIT WORKFLOW ===
# On the branch to merge:
git checkout main
git pull --rebase
git merge --no-ff <branch-name>
# Or if clean history preferred:
git rebase main <branch-name> && git checkout main && git merge --ff-only <branch-name>
# Delete the merged branch
git branch -d <branch-name>
# === JJ WORKFLOW ===
jj rebase -d main@origin
jj git push --bookmark <name>
Post-merge verification:
# Re-run quality gates on main after merge
# This catches integration issues
git checkout main # Ensure you're on main
make test || cargo test || gleam test || npm test
# If using zjj done, it already switched you to main
# Just verify tests pass:
cargo test || gleam test || npm test
If merge introduces failures:
Post-merge tests fail?
├─ Merge conflict resolution error → Fix, recommit
├─ Integration issue → Fix now if quick, or:
│ ├─ zjj users: No undo needed, workspace is gone. Fix in main.
│ └─ git users: Revert merge, file bead, fix in new branch
└─ Pre-existing → File bead (should have been caught in Step 3)
ZJJ MERGE QUEUE ADVANTAGES:
- Single command -
zjj donehandles sync, merge, push, cleanup - Atomic - Either completes fully or fails with workspace intact
- Clean - Removes workspace and Zellij session automatically
- Safe - Can
zjj syncto preview beforezjj done - Tracked - All workspaces visible in
zjj list
Required Outputs:
- All completed branches/workspaces merged to main
- Merged branches/workspaces deleted/removed
- Quality gates pass on main after merge
- No orphan branches/workspaces remain (except documented in-progress work)
- All zjj sessions cleaned up for merged work
Step 5: Update Issue/Bead Status
Objective: Every issue/bead reflects reality.
Actions:
# List all open beads assigned to you
br list --status open --assignee @me
# List beads in progress
br list --status in_progress
# Close completed beads with reason
br close <bead-id> --reason "Completed: <summary of what was done>"
br close <bead-id> --suggest-next # close and show what's unblocked
# Update in-progress beads with current state
br update <bead-id> --notes "Session status: <current state>"
br update <bead-id> --status in_progress
# Verify new beads from Step 2 are well-formed
br lint
# Check dependency graph for cycles
br dep cycles
# Show what's ready for next session
br ready
Required Outputs:
- Completed beads closed with reason
- In-progress beads updated with current state
- New beads from Step 2 are filed, labeled, and pass
br lint - Dependencies wired (
br dep) and no cycles -
br readyshows accurate next-up work
Step 6: Push to Remote (MANDATORY)
Objective: Everything on remote. Nothing local-only.
# === JJ (preferred) ===
jj git fetch # fetch first — rebase happens automatically
jj git push --bookmark main
# === GIT (fallback) ===
git pull --rebase
git push
# Verify
git log --branches --not --remotes
# Expected: empty (no unpushed commits)
git status
# MUST show: "Your branch is up to date with 'origin/main'"
Push failures use exponential backoff — see the Exponential Backoff Protocol in Core Principles (push pseudocode is there). Same schedule: 2→4→8→16→32→60s, max 6 retries. jj git fetch before each retry — a non-fast-forward rejection often self-resolves after a fresh fetch + rebase. Permission denied or branch protection: STOP immediately, backoff won't help.
CRITICAL RULES:
- Work is NOT complete until push succeeds
- NEVER stop before pushing
- NEVER say "ready to push when you are" — YOU must push
- If push fails, use backoff. If backoff exhausts, flag the user — do not spin forever
Required Outputs:
-
git pushsucceeded -
git statusshows up to date - No unpushed commits remain
Step 7: Clean Up Orphans
Objective: No dangling state. Main is the only thing left.
# === ZJJ WORKSPACES (CHECK FIRST) ===
# List all zjj workspaces
zjj list 2>/dev/null
# For each workspace, decide:
# - Work complete → zjj done -w <name> (merges and removes)
# - Work incomplete → File bead, then zjj abort -w <name>
# - Work abandoned → zjj abort -w <name>
# Clean up completed/abandoned workspaces
zjj clean --dry-run 2>/dev/null # Preview what will be cleaned
zjj clean 2>/dev/null # Remove stale workspaces
# Verify no sessions remain
SESSIONS=$(zjj list 2>/dev/null | wc -l)
if [ "$SESSIONS" -gt 0 ]; then
echo "WARNING: $SESSIONS zjj workspaces still active"
zjj list 2>/dev/null
echo "Each must be completed (zjj done -w <name>), aborted (zjj abort -w <name>), or documented as in-progress"
fi
# === VERIFY WORKSPACE DIRECTORIES ARE ACTUALLY GONE ===
# zjj list may show clean, but the directory could still be on disk
# (zjj done cleans up, but failures or interruptions can leave orphans)
WORKSPACE_BASE=$(zjj config workspace_dir 2>/dev/null || echo "../lewis__workspaces")
if [ -d "$WORKSPACE_BASE" ]; then
LEFTOVER_DIRS=$(ls -d "$WORKSPACE_BASE"/*/ 2>/dev/null | wc -l)
if [ "$LEFTOVER_DIRS" -gt 0 ]; then
echo "WARNING: $LEFTOVER_DIRS workspace directories still on disk in $WORKSPACE_BASE:"
ls -la "$WORKSPACE_BASE"/
echo "These are orphans. Each must be traced to a session (zjj list --all) or removed manually."
fi
fi
# === GIT BRANCHES ===
# Delete all branches merged into main
git branch --merged main | grep -v "\*\|main\|master" | xargs -n 1 git branch -d 2>/dev/null
# Flag unmerged branches (should have been handled in Step 1)
UNMERGED=$(git branch --no-merged main | grep -v "\*" | tr -d ' ')
if [ -n "$UNMERGED" ]; then
echo "WARNING: Unmerged branches remain: $UNMERGED"
echo "Each must have a bead filed or be explicitly abandoned."
fi
# === GIT WORKTREES ===
# Worktrees should be managed by zjj, but check for orphans
git worktree prune
WORKTREES=$(git worktree list | grep -v "$(git rev-parse --show-toplevel)" | wc -l)
if [ "$WORKTREES" -gt 0 ]; then
echo "WARNING: $WORKTREES worktrees still exist (not tracked by zjj)"
git worktree list
echo "These should be removed: git worktree remove <path>"
fi
# === STASHES ===
STASH_COUNT=$(git stash list | wc -l)
if [ "$STASH_COUNT" -gt 0 ]; then
echo "WARNING: $STASH_COUNT stashes exist"
git stash list
echo "Stashes should be applied, filed as bead, or dropped"
fi
# === REMOTE BRANCHES ===
git remote prune origin
# === TEMP FILES ===
git clean -n # Dry run
# If safe: git clean -fd
FLAG THE USER for any remaining:
- Unmerged branches (must have beads or be abandoned)
- Open zjj workspaces (must be completed with
zjj done, in-progress with bead, or removed) - Orphan worktrees not tracked by zjj (must be removed with
git worktree remove) - Stashes older than today (must be applied, filed as bead, or dropped)
ZJJ CLEANUP DECISION TREE:
zjj workspace found?
├─ Work is COMPLETE → zjj done -w <name> (merges, pushes, cleans up)
├─ Work is INCOMPLETE but VALUABLE → File bead, then:
│ ├─ Want to resume soon → Keep workspace, document in handoff
│ └─ Not resuming → zjj abort -w <name> (proper discard, updates bead)
├─ Work is ABANDONED → zjj abort -w <name>
└─ Workspace is STALE (>7 days) → Flag user, likely abandoned
Note: zjj done -w <name> and zjj abort -w <name> both work from main — no need to cd into each workspace during the audit.
Required Outputs:
- All zjj workspaces either completed (zjj done) or removed
- Merged git branches deleted
- Worktrees pruned
- Stashes reviewed and cleared
- Remote branches pruned
- Any remaining orphans flagged to user with explanation
Step 8: Final Verification (Main Is Clean)
Objective: Prove main is in a pristine state.
# 1. On main
git checkout main 2>/dev/null || jj edit main 2>/dev/null
# 2. Working tree is clean
git status
# Expected: "nothing to commit, working tree clean"
# 3. All commits pushed
git log --branches --not --remotes
# Expected: empty
# 4. Quality gates pass on main
make test || cargo test || gleam test || npm test
cargo clippy -- -D warnings 2>/dev/null
cargo fmt --check 2>/dev/null || gleam format --check src/ test/ 2>/dev/null
# 5. No uncommitted changes
git diff --quiet && git diff --cached --quiet
# 6. No orphan branches
ORPHANS=$(git branch --no-merged main | grep -v "\*" | wc -l)
echo "Unmerged branches: $ORPHANS"
# 7. No dangling worktrees (zjj-managed or orphaned)
WORKTREES=$(git worktree list | grep -v "$(git rev-parse --show-toplevel)" | wc -l)
echo "Extra worktrees: $WORKTREES"
# 8. No zjj workspaces remaining (all should be merged or removed)
WORKSPACES=$(zjj list 2>/dev/null | wc -l)
echo "Active zjj workspaces: $WORKSPACES"
if [ "$WORKSPACES" -gt 0 ]; then
echo "WARNING: zjj workspaces still active:"
zjj list 2>/dev/null
fi
# 9. No stashes
STASHES=$(git stash list | wc -l)
echo "Stashes: $STASHES"
# 10. Verify you're not in a zjj session
CURRENT_SESSION=$(echo $ZELLIJ_SESSION_NAME 2>/dev/null)
if [ -n "$CURRENT_SESSION" ] && [[ "$CURRENT_SESSION" == zjj-* ]]; then
echo "WARNING: Still in zjj Zellij session: $CURRENT_SESSION"
echo "Exit the session or use zjj attach to switch to main session"
fi
PASS criteria (ALL must be true):
Main Is Clean Checklist:
[PASS/FAIL] Working tree clean
[PASS/FAIL] All commits pushed
[PASS/FAIL] Tests passing
[PASS/FAIL] Zero lint violations
[PASS/FAIL] Zero warnings
[PASS/FAIL] Code formatted
[PASS/FAIL] No orphan branches (or all have beads)
[PASS/FAIL] No dangling worktrees
[PASS/FAIL] No active zjj workspaces (all merged via zjj done or removed)
[PASS/FAIL] No stale stashes
[PASS/FAIL] Not in a zjj session (should be in main session or no session)
If ANY check fails at this point:
Final verification failure?
├─ Quality gate failure → MUST fix or revert. Cannot land dirty.
├─ Orphan branch → File bead if not already done. Flag user.
├─ Dangling worktree → Remove with git worktree remove
├─ Active zjj workspace → Either:
│ ├─ Work complete → zjj done -w <name>
│ ├─ Work in-progress → Document in handoff, keep (exception)
│ └─ Work abandoned → zjj abort -w <name>
├─ Stale stash → Apply, file as bead, or drop. Flag user.
└─ In zjj session → Exit session or zjj attach to main
Required Outputs:
- Every check PASS
- Main is provably clean
- No zjj workspaces remain (or documented as in-progress)
- Not in a zjj Zellij session
- Any exceptions documented and flagged
Step 9: Bead Reconciliation
Objective: Ensure every smell surfaced during landing has been properly tracked.
# List all open beads — these should include everything filed this session
br list --status open
# Lint all open beads for missing required sections
# br lint enforces: Acceptance Criteria (task/feature), Steps to Reproduce (bug)
br lint
# For each bead, verify EARS + BDD sections are present:
# - Description has: Context, Smell Classification, Dependencies, Requirements (EARS), Variants
# - Acceptance has: High-Level Criteria, BDD Scenarios (Given/When/Then), Verification checklist
# If sparse, enrich now:
br update <bead-id> --description "..." --acceptance "..."
# Check dependency graph is clean
br dep cycles
# Verify dependencies are wired
br dep list <bead-id> # for each bead with known relationships
# Show what's ready for next session
br ready --pretty
Reconciliation check:
For each quality gate failure that was filed (not fixed):
├─ Bead exists via br show <id>? → Good
├─ Bead exists but missing EARS/BDD sections? → Enrich NOW:
│ br update <id> --description "..." --acceptance "..."
├─ No bead filed? → File it now with full template. This is BLOCKING.
│ br create "[smell-type] ..." --description "..." --acceptance "..."
└─ br lint passes for this bead? → Good. If not, fix sections.
For each orphan branch/worktree/session that remains:
├─ Bead exists explaining why it's open? → Good
├─ No bead? → File one or remove the orphan. No middle ground.
│ br create "[process] Orphan: ..." --labels "smell:process"
└─ Dependency wired to parent work? → br dep add <orphan-bead> <parent-bead>
Required Outputs:
- Every unfixed failure has a bead with full EARS + BDD template
-
br lintpasses on all open beads - Dependencies wired with
br dep— no cycles (br dep cycles) - Every remaining orphan has a bead or has been removed
-
br ready --prettyshows accurate next-up work
Step 10: Hand Off
Objective: Next session knows exactly where things stand.
Handoff Report:
## Session Complete — Landing Report
### Work Completed
- [List of features/fixes implemented]
- [Commits pushed: X commits]
- [Issues/beads closed: #123, #456]
### Main Status
- Branch: main
- Quality Gates: ALL PASSING | EXCEPTIONS NOTED
- Tests: [count] passing, [count] failing (beads filed)
- Lint: clean | [count] warnings (beads filed)
- Warnings: zero | [count] (beads filed)
- Format: clean
- Remote Sync: up to date
### Smells Surfaced (Beads Filed)
- [bead-id]: [smell type] — [brief description] — [severity]
- [bead-id]: [smell type] — [brief description] — [severity]
### Orphans Remaining (with justification)
- Branch `feature-x`: In-progress, bead [id] tracks it, ETA next session
- zjj session `experiment`: Paused, bead [id], will resume or abandon
### Cleanup Performed
- Branches deleted: [list]
- Worktrees removed: [list]
- Sessions closed: [list]
- Stashes cleared: [count]
### Next Steps
- [What should be done next]
- [Any blockers or dependencies]
- [Which beads to tackle first]
### Notes
- [Technical decisions made this session]
- [Known issues or workarounds]
- [Context that won't be obvious next time]
Required Outputs:
- Summary of work completed
- Main status with quality gate results
- All beads listed with smell types
- All orphans justified or removed
- Next steps clear
- Important context captured
Landing Checklist (Full)
Run through this before ending ANY session:
Session Landing Checklist:
Step 1: Audit Orphans
[ ] All branches inventoried
[ ] All worktrees accounted for
[ ] All zjj sessions accounted for
[ ] All stashes reviewed
[ ] User flagged for any anomalies
Step 2: File Beads for Remaining Work
[ ] TODOs/FIXMEs catalogued
[ ] Beads created for every incomplete item
[ ] Each bead has smell type and severity
Step 3: Quality Gates (ZERO TOLERANCE)
[ ] Tests passing (zero failures)
[ ] Linting clean (zero warnings, -D warnings)
[ ] Zero compiler/runtime warnings
[ ] Formatting clean
[ ] Build succeeds
[ ] Type checking passes
[ ] Failures either fixed or filed as beads
Step 4: Merge to Main
[ ] All completed branches merged
[ ] Merged branches deleted
[ ] Quality gates pass post-merge
Step 5: Update Issue/Bead Status
[ ] Completed beads closed
[ ] In-progress beads updated
[ ] New beads filed and tagged
Step 6: Push to Remote (MANDATORY)
[ ] git push succeeded
[ ] Branch up to date with remote
[ ] No unpushed commits
Step 7: Clean Up Orphans
[ ] Merged branches deleted
[ ] Worktrees pruned
[ ] zjj sessions cleaned
[ ] Stashes cleared
[ ] Remote branches pruned
Step 8: Final Verification
[ ] Main is clean (all checks PASS)
[ ] Working tree clean
[ ] Tests pass on main
[ ] Zero lint/warnings on main
Step 9: Bead Reconciliation
[ ] Every unfixed failure has a bead
[ ] Every orphan has a bead or is removed
[ ] All beads have sufficient context
Step 10: Hand Off
[ ] Landing report written
[ ] Main status documented
[ ] Smells listed with beads
[ ] Next steps clear
Failure Scenarios and Recovery
Scenario 1: Tests Fail
Problem: Tests fail during quality gates
Smell Type: test | code | design
Recovery:
1. Read test output — understand the failure
2. Is this a new failure (introduced this session)?
YES → Fix it now. This is YOUR responsibility.
NO → File bead as pre-existing, continue landing.
3. If fixing: fix, re-run ALL gates, commit the fix
4. If filing: bead must include exact test output, file path, and context
NEVER push with failing tests you introduced
Pre-existing failures get beads but don't block landing
Scenario 2: Linter Warnings
Problem: Clippy/ESLint/etc. reports warnings
Smell Type: code
Recovery:
1. Read each warning
2. Quick fix (< 2 min each)? → Fix, commit, re-run
3. Suppression justified? → Add allow with comment explaining why, commit
4. Complex fix needed? → File bead with smell type "code", severity "important"
STANDARD: -D warnings (warnings are errors)
`
…(truncated)