refactor: make cmd_continue idempotent with ensure_* functions #168

Closed
opened 2026-04-06 02:32:12 +02:00 by shoko · 0 comments
Owner

Summary

Simplify kugetsu by making cmd_continue the main entry point for session/worktree management. Extract reusable functions so cmd_start becomes a thin wrapper and the daemon can always call cmd_continue without existence checks.

Problem

Currently cmd_start and cmd_continue have duplicate existence-checking logic:

  • cmd_start - creates new worktree + session
  • cmd_continue - continues existing session, errors if doesn't exist

The daemon must check existence before calling either:

if worktree_exists "$issue_ref" || session_exists; then
    cmd_continue "$issue_ref" "$message"
else
    cmd_start "$issue_ref" "$message"
fi

This is hard to maintain and cmd_continue doesn't handle the "auto-create" case.

Solution

New Functions (in kugetsu-session.sh)

1. ensure_worktree(issue_ref)

  • Check if worktree exists for issue
  • If exists → log info, return "existed"
  • If missing → create worktree
  • Return: "created" | "existed" | "error"

2. ensure_session(issue_ref)

  • Check if session exists for issue
  • If session exists → log info, return "continued"
  • If session missing → create from base
  • If inconsistent state (worktree without session or vice versa) → clean and recreate (idempotent)
  • Return: "created" | "continued" | "error"

3. fork_agent(session_id, worktree_path, message)

  • Change to worktree directory
  • Write message to .kugetsu-msg.txt
  • Run opencode in background with nohup
  • Log to LOGS_DIR/dev-{session}.log
  • Return: "forked" | "error"

4. cmd_continue() refactored

cmd_continue() {
    local issue_ref="${1:-}"
    local message="${2:-}"
    
    validate_issue_ref "$issue_ref"
    [ -z "$message" ] && message=$(build_dev_agent_message "$issue_ref" "")
    
    # Ensure resources exist (idempotent)
    worktree_status=$(ensure_worktree "$issue_ref")
    session_status=$(ensure_session "$issue_ref")
    
    # Get session details
    local session_path="$SESSIONS_DIR/$(issue_ref_to_filename "$issue_ref")"
    local opencode_session_id=$(python3 -c "import json; print(json.load(open('$session_path')).get('opencode_session_id', ''))")
    local worktree_path=$(python3 -c "import json; print(json.load(open('$session_path')).get('worktree_path', ''))")
    
    # Fork agent
    fork_status=$(fork_agent "$opencode_session_id" "$worktree_path" "$message")
    
    # Log combined status
    log "info" "cmd_continue" "Result for $issue_ref: worktree=$worktree_status session=$session_status fork=$fork_status"
    
    # Return status for caller
    echo "${worktree_status}-${session_status}-${fork_status}"
}

5. cmd_start() becomes thin wrapper

cmd_start() { cmd_continue "$@"; }

Files to Modify

  1. skills/kugetsu/scripts/kugetsu-session.sh

    • Add ensure_worktree() function
    • Add ensure_session() function
    • Add fork_agent() function
    • Refactor cmd_continue() to use the above
    • Make cmd_start() a thin wrapper
  2. skills/kugetsu/scripts/kugetsu-queue-daemon.sh

    • Remove existence check at lines 112-133
    • Always call cmd_continue "$issue_ref" "$message"

CLI Behavior Changes

Command Before After
kugetsu start <issue> "msg" Creates new worktree+session Same (calls cmd_continue)
kugetsu continue <issue> "msg" (exists) Continues existing Same
kugetsu continue <issue> "msg" (not exists) Errors "No session found" Auto-creates session+worktree

Idempotent Retry Behavior

If any operation fails mid-way, retry will succeed because:

  • ensure_worktree() detects existing worktree → uses it
  • ensure_session() detects missing session → creates it

No manual cleanup needed before retry.

Testing Scenarios

  1. Fresh start: kugetsu start github.com/user/repo#1 "new task" on issue with no worktree/session

    • Expected: worktree="created", session="created", fork="forked"
  2. Continue existing: kugetsu continue github.com/user/repo#1 "resume" on issue with existing worktree/session

    • Expected: worktree="existed", session="continued", fork="forked"
  3. Continue on fresh issue (new behavior): kugetsu continue github.com/user/repo#1 "msg" when nothing exists

    • Expected: worktree="created", session="created", fork="forked"
    • Before: Would error "No session found"
  4. Partial failure recovery: Session creation fails, then retry succeeds

    • Expected: On retry, existing worktree is reused, session is created
  5. Daemon delegate: Daemon processes a pending task

    • Expected: Works without existence check
  6. Inconsistent state recovery: Worktree exists but session missing

    • Expected: ensure_session() detects and recreates both cleanly

Deprecation Path

  • cmd_start will eventually be deprecated (not in this PR)
  • Future: Users only need to know kugetsu continue
  • For now: Both commands work, cmd_start is just a thin wrapper

Acceptance Criteria

  • ensure_worktree() returns correct status and is idempotent
  • ensure_session() returns correct status and is idempotent
  • fork_agent() correctly starts opencode session in background
  • cmd_start works exactly as before (thin wrapper)
  • kugetsu continue <issue> auto-creates if session/worktree missing
  • Daemon works without existence check
  • All 6 testing scenarios pass
  • Partial failure recovery works (retry succeeds)
## Summary Simplify kugetsu by making `cmd_continue` the main entry point for session/worktree management. Extract reusable functions so `cmd_start` becomes a thin wrapper and the daemon can always call `cmd_continue` without existence checks. ## Problem Currently `cmd_start` and `cmd_continue` have duplicate existence-checking logic: - `cmd_start` - creates new worktree + session - `cmd_continue` - continues existing session, errors if doesn't exist The daemon must check existence before calling either: ```bash if worktree_exists "$issue_ref" || session_exists; then cmd_continue "$issue_ref" "$message" else cmd_start "$issue_ref" "$message" fi ``` This is hard to maintain and `cmd_continue` doesn't handle the "auto-create" case. ## Solution ### New Functions (in `kugetsu-session.sh`) **1. `ensure_worktree(issue_ref)`** - Check if worktree exists for issue - If exists → log info, return "existed" - If missing → create worktree - Return: "created" | "existed" | "error" **2. `ensure_session(issue_ref)`** - Check if session exists for issue - If session exists → log info, return "continued" - If session missing → create from base - If inconsistent state (worktree without session or vice versa) → clean and recreate (idempotent) - Return: "created" | "continued" | "error" **3. `fork_agent(session_id, worktree_path, message)`** - Change to worktree directory - Write message to `.kugetsu-msg.txt` - Run opencode in background with nohup - Log to `LOGS_DIR/dev-{session}.log` - Return: "forked" | "error" **4. `cmd_continue()` refactored** ```bash cmd_continue() { local issue_ref="${1:-}" local message="${2:-}" validate_issue_ref "$issue_ref" [ -z "$message" ] && message=$(build_dev_agent_message "$issue_ref" "") # Ensure resources exist (idempotent) worktree_status=$(ensure_worktree "$issue_ref") session_status=$(ensure_session "$issue_ref") # Get session details local session_path="$SESSIONS_DIR/$(issue_ref_to_filename "$issue_ref")" local opencode_session_id=$(python3 -c "import json; print(json.load(open('$session_path')).get('opencode_session_id', ''))") local worktree_path=$(python3 -c "import json; print(json.load(open('$session_path')).get('worktree_path', ''))") # Fork agent fork_status=$(fork_agent "$opencode_session_id" "$worktree_path" "$message") # Log combined status log "info" "cmd_continue" "Result for $issue_ref: worktree=$worktree_status session=$session_status fork=$fork_status" # Return status for caller echo "${worktree_status}-${session_status}-${fork_status}" } ``` **5. `cmd_start()` becomes thin wrapper** ```bash cmd_start() { cmd_continue "$@"; } ``` ### Files to Modify 1. **`skills/kugetsu/scripts/kugetsu-session.sh`** - Add `ensure_worktree()` function - Add `ensure_session()` function - Add `fork_agent()` function - Refactor `cmd_continue()` to use the above - Make `cmd_start()` a thin wrapper 2. **`skills/kugetsu/scripts/kugetsu-queue-daemon.sh`** - Remove existence check at lines 112-133 - Always call `cmd_continue "$issue_ref" "$message"` ### CLI Behavior Changes | Command | Before | After | |---------|--------|-------| | `kugetsu start <issue> "msg"` | Creates new worktree+session | Same (calls cmd_continue) | | `kugetsu continue <issue> "msg"` (exists) | Continues existing | Same | | `kugetsu continue <issue> "msg"` (not exists) | Errors "No session found" | Auto-creates session+worktree | ### Idempotent Retry Behavior If any operation fails mid-way, retry will succeed because: - `ensure_worktree()` detects existing worktree → uses it - `ensure_session()` detects missing session → creates it No manual cleanup needed before retry. ### Testing Scenarios 1. **Fresh start**: `kugetsu start github.com/user/repo#1 "new task"` on issue with no worktree/session - Expected: worktree="created", session="created", fork="forked" 2. **Continue existing**: `kugetsu continue github.com/user/repo#1 "resume"` on issue with existing worktree/session - Expected: worktree="existed", session="continued", fork="forked" 3. **Continue on fresh issue** (new behavior): `kugetsu continue github.com/user/repo#1 "msg"` when nothing exists - Expected: worktree="created", session="created", fork="forked" - Before: Would error "No session found" 4. **Partial failure recovery**: Session creation fails, then retry succeeds - Expected: On retry, existing worktree is reused, session is created 5. **Daemon delegate**: Daemon processes a pending task - Expected: Works without existence check 6. **Inconsistent state recovery**: Worktree exists but session missing - Expected: `ensure_session()` detects and recreates both cleanly ## Deprecation Path - `cmd_start` will eventually be deprecated (not in this PR) - Future: Users only need to know `kugetsu continue` - For now: Both commands work, `cmd_start` is just a thin wrapper ## Acceptance Criteria - [ ] `ensure_worktree()` returns correct status and is idempotent - [ ] `ensure_session()` returns correct status and is idempotent - [ ] `fork_agent()` correctly starts opencode session in background - [ ] `cmd_start` works exactly as before (thin wrapper) - [ ] `kugetsu continue <issue>` auto-creates if session/worktree missing - [ ] Daemon works without existence check - [ ] All 6 testing scenarios pass - [ ] Partial failure recovery works (retry succeeds)
shoko added the blocked label 2026-04-07 08:55:24 +02:00
shoko changed title from enhancement: make cmd_continue idempotent to refactor: make cmd_continue idempotent with ensure_* functions 2026-04-07 10:09:58 +02:00
shoko added ready and removed blocked labels 2026-04-07 10:10:13 +02:00
shoko closed this issue 2026-04-08 00:32:16 +02:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: shoko/kugetsu#168