Skip to content

Add opt-in current-checkout mode for autonomous runs#2

Open
sigmundas wants to merge 2 commits into
quaat:mainfrom
sigmundas:feature/current-branch-mode
Open

Add opt-in current-checkout mode for autonomous runs#2
sigmundas wants to merge 2 commits into
quaat:mainfrom
sigmundas:feature/current-branch-mode

Conversation

@sigmundas

Copy link
Copy Markdown

Summary

Adds an explicit opt-in current-checkout execution mode for autonomous runs.

The existing isolated-worktree workflow remains the default. This adds a second mode for users who already created their own feature branch and want the autonomous run to operate directly in that checkout, so normal git diff, local testing, and manual commit flow still work.

What changed

  • Added controller.py init --worktree-mode isolated|current
    • isolated remains the default behavior.
    • current uses the current project root as the run worktree.
  • Added --allow-main guard override for current-checkout mode.
  • Current-checkout mode refuses:
    • main / master unless --allow-main is passed
    • dirty working trees
  • Current-checkout mode records the current branch and baseline commit in run metadata.
  • Status output now shows the selected worktree mode.
  • Legacy / migrated runs default to isolated mode.
  • Updated docs and examples to describe both worktree modes.
  • Updated the autonomous-feature skill docs so current-checkout mode skips the disposable worktree entry flow.

Why

The existing isolated worktree model is safe and should remain the default, but it is awkward when the user already has a prepared branch and wants to watch normal local diffs, run project-specific manual tests, and commit manually.

This mode is meant for that manual-branch workflow without weakening the default safety model.

Safety behavior

Current-checkout mode is deliberately opt-in.

It does not create .claude/worktrees/* or worktree-* branches.

It fails closed on risky starting states:

  • dirty tree
  • main / master branch unless explicitly overridden
  • repository drift is still tracked through the existing baseline/drift machinery

Tests

Ran:

./.venv/bin/python -m unittest tests.test_state
./.venv/bin/python -m unittest tests.test_project_layout
PATH="$PWD/.venv/bin:$PATH" ./.venv/bin/python -m unittest tests.test_controller

All passed.

Also smoke-tested current-checkout mode manually in a throwaway repository:

  • initialized a run on a non-main feature branch with --worktree-mode current
  • verified edits landed directly in the active checkout
  • verified no .claude/worktrees/* worktree was created
  • verified HEAD stayed unchanged until manual commit
  • verified dirty-tree refusal
  • verified main/master refusal unless --allow-main

@sigmundas

Copy link
Copy Markdown
Author

Added a follow-up UX layer on top of the current-checkout controller mode.

New slash-command skills:

  • autonomous-current

    • For direct edits on an already-created clean non-main feature branch.
    • Uses controller.py init --mode standard --worktree-mode current.
    • Does not create or enter .claude/worktrees.
    • Does not commit.
  • autonomous-main

    • For explicit direct edits on main/master.
    • Uses controller.py init --mode standard --worktree-mode current --allow-main.
    • Still requires a clean tree.
    • Does not create or enter .claude/worktrees.
    • Does not commit.

Existing autonomous-feature behavior remains the safe isolated-worktree default.

Validation run:

  • tests.test_state — passed, 41 ok / 1 skipped
  • tests.test_project_layout — passed, 8 ok
  • tests.test_controller — 85 ok / 1 pre-existing macOS temp-path failure

The remaining controller failure is LegacyMigrationIntegrityTests.test_migrate_creates_run_from_legacy_source, caused by /private/var/... vs /var/... path spelling on macOS in an assertion on migrated_from. I verified the same failure occurs on a clean tree before these edits, so it is unrelated to the current-checkout/slash-command changes.

@quaat quaat left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the current-checkout mode. Clean, well-tested, and fail-closed — approving in spirit. Three minor inline notes below; only the detached-HEAD one affects the main guard's reliability.

Comment thread scripts/controller.py
worktree_mode = getattr(args, "worktree_mode", "isolated")
dirty = git(repo.canonical_root, "status", "--short", check=False).splitlines()
if worktree_mode == "current":
if repo.branch in {"main", "master"} and not getattr(args, "allow_main", False):

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Detached HEAD bypasses this guard. repo.branch comes from git branch --show-current, which returns an empty string on a detached HEAD. So if the user is in detached-HEAD state (even sitting exactly on the tip of main), repo.branch in {"main", "master"} is False and current-checkout mode proceeds without --allow-main. Consider also refusing an empty branch name here (or explicitly documenting detached HEAD as out of scope).

Comment thread scripts/controller.py
ctx_text, encoding="utf-8"
)
worktree_mode = getattr(args, "worktree_mode", "isolated")
dirty = git(repo.canonical_root, "status", "--short", check=False).splitlines()

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

git status --short reports untracked files (??) too, so a tree that is clean except for benign untracked files will be refused below as 'not a clean working tree'. That matches the intended fail-closed posture, but the error message may surprise users who only have untracked files — worth a word in the message or docs. (Also note the docs/skills say git status --porcelain; equivalent for emptiness, just flagging the wording mismatch.)

Comment thread scripts/controller.py
worktree_mode = getattr(args, "worktree_mode", "isolated")
dirty = git(repo.canonical_root, "status", "--short", check=False).splitlines()
if worktree_mode == "current":
if repo.branch in {"main", "master"} and not getattr(args, "allow_main", False):

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Separately: --allow-main is silently ignored when --worktree-mode is isolated (no validation ties the two together). Harmless, but a no-op flag can mislead — consider warning if --allow-main is passed without --worktree-mode current.

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.

2 participants