# Incident Report: vectl Plan Divergence

**Incident ID:** INC-2026-03-07-001  
**Date:** 2026-03-07  
**Time Range:** 23:24 - 23:50 CST (+0800)  
**Severity:** Medium  
**Status:** Resolved (Phase 1 Controls Implemented)  

---

## Executive Summary

On 2026-03-07, a divergence occurred between vectl's canonical `plan.yaml` and its state tracking in `.git/vectl/state.json`. Evidence shows a `reset: moving to HEAD` operation at 23:47:14 CST, resulting in `plan.yaml` reverting to an earlier state while `state.json` preserved more recent progress tracking. The root cause is consistent with a subagent performing git operations on a shared worktree where uncommitted `plan.yaml` changes were discarded.

**What is certain:** A reset occurred; `plan.yaml` is now stale relative to `state.json`.  
**What is uncertain:** Whether the reset was `--hard` or `--mixed`, whether it was intentional, and which subagent executed it.

---

## Observed Symptoms

1. **State Divergence:** `plan.yaml` shows earlier state while `.git/vectl/state.json` contains more recent progress
2. **Dirty Status Before Incident:** Evidence from `state.json` clipboard shows `plan.yaml` was modified at ~23:24 and ~23:25
3. **Reset Operation Detected:** Git reflog shows `reset: moving to HEAD` at 2026-03-07 23:47:14 CST
4. **Phase Status Mismatch:** Phase `talk-ux-bugs-v3` shows `done` in `state.json` but `pending` in `plan.yaml`
5. **File Modification Order:** `state.json` modified at 23:38:24, `plan.yaml` at 23:39:36 (both before the 23:47 reset)

---

## Confirmed Facts

| Fact | Source | Confidence |
|------|--------|------------|
| `reset: moving to HEAD` at 23:47:14 CST | `.git/logs/HEAD`, `git reflog` | **Confirmed** |
| Commit `0a7a94fb` was HEAD before/after reset | `git reflog` | **Confirmed** |
| `plan.yaml` modified at 23:39:36 (mtime) | Filesystem metadata | **Confirmed** |
| `state.json` modified at 23:38:24 (mtime) | Filesystem metadata | **Confirmed** |
| Phase `talk-ux-bugs-v3` completed per `state.json` | `.git/vectl/state.json` phases section | **Confirmed** |
| Step `cli-chat-command.extract-agent-options-decorator` done | `.git/vectl/state.json` | **Confirmed** (timestamp inferred ~23:30) |
| Current `plan.yaml` shows `talk-ux-bugs-v3` as pending | `plan.yaml` content | **Confirmed** |

### Inferred (Not Confirmed)

| Item | Inference | Confidence |
|------|-----------|------------|
| Reset was `--hard` | Working tree changes lost, consistent with `--hard` | **Medium** |
| Subagent actor | Shared worktree + git operation pattern | **Medium** |
| Intent | Unknown — could be accidental or cleanup | **Unknown** |

---

## Timeline

All times in CST (+0800). Events marked **CONFIRMED** have direct evidence; **INFERRED** are reconstructed.

| Time | Event | Evidence | Status |
|------|-------|----------|--------|
| ~23:24 | `plan.yaml` marked dirty (modified) | `state.json` clipboard entry | **CONFIRMED** |
| ~23:25 | `plan.yaml` still dirty | `state.json` clipboard entry | **CONFIRMED** |
| ~23:30 | Step `cli-chat-command.extract-agent-options-decorator` completed | `state.json` step timestamp | **INFERRED** |
| 23:38:24 | `state.json` modified | Filesystem mtime | **CONFIRMED** |
| 23:39:36 | `plan.yaml` modified | Filesystem mtime | **CONFIRMED** |
| 23:39:44 | Commit `7b689fe`: "fix(cli): change spawn --lifecycle default..." | Git log, timestamp 1772893184 | **CONFIRMED** |
| 23:46:51 | Commit `0a7a94fb`: "feat(cli): add chat command orchestration" | Git log, timestamp 1772898411 | **CONFIRMED** |
| **23:47:14** | **`reset: moving to HEAD`** | `.git/logs/HEAD` line 26, timestamp 1772898434 | **CONFIRMED** |
| 23:47:26 | Commit `9016038`: "refactor(cli): replace --sandbox/--no-sandbox..." | Git log, timestamp 1772898446 | **CONFIRMED** |
| 23:48:40 | Commit `3c97fc9`: "test(cli): add chat cleanup integration coverage" | Git log, timestamp 1772898520 | **CONFIRMED** |
| 23:49:51 | Commit `6eae350`: "test(cli): update expected help text..." | Git log, timestamp 1772898591 | **CONFIRMED** |

### Timeline Notes

- The reset at 23:47:14 occurred **between** commits `0a7a94fb` (23:46:51) and `9016038` (23:47:26)
- The ~20-minute gap between `state.json` modification (23:38:24) and first commit (23:39:44) suggests active work
- All commits after reset continue the same feature work (cli chat command), suggesting the reset did not disrupt the subagent's workflow intent

---

## Evidence Sources Used

### Primary Evidence

| Source | Location | Purpose |
|--------|----------|---------|
| `git reflog` | `.git/logs/refs/heads/main` | Commit sequence and reset detection |
| `.git/logs/HEAD` | `.git/logs/HEAD` | Detailed operation log with timestamps |
| `.git/vectl/state.json` | `.git/vectl/state.json` | Step statuses, timestamps, clipboard evidence |
| `plan.yaml` | `plan.yaml` | Current (stale) plan state |
| Filesystem metadata | `stat plan.yaml`, `stat .git/vectl/state.json` | Modification times |
| Git commit history | `git log` | Commit sequence and timestamps |

### Evidence Details

**Git Reflog Entry (Confirmed):**
```
0a7a94fb8995c0efe63df74438810476698e6079 0a7a94fb8995c0efe63df74438810476698e6079 \
  Zhu Zhaomeng <zhaomeng.zhu@gmail.com> 1772898434 +0800	reset: moving to HEAD
```

**State.json Key Entries (Confirmed):**
```json
"cli-chat-command.extract-agent-options-decorator": {
  "status": "done",
  "claimed_at": "2026-03-07T15:30:00+00:00"
},
"talk-ux-bugs-v3": { "status": "done" }
```

**File Modification Times (Confirmed):**
- `state.json`: 2026-03-07 23:38:24 CST
- `plan.yaml`: 2026-03-07 23:39:36 CST

---

## Detailed Event List

1. **Event 1 (Confirmed):** Subagent activity beginning ~23:24, `plan.yaml` becomes dirty
2. **Event 2 (Confirmed):** Step `cli-chat-command.extract-agent-options-decorator` completed per `state.json`
3. **Event 3 (Confirmed):** `state.json` written at 23:38:24 preserving progress
4. **Event 4 (Confirmed):** `plan.yaml` modified at 23:39:36 (uncommitted changes)
5. **Event 5 (Confirmed):** Commits begin at 23:39:44 continuing through 23:46:51
6. **Event 6 (Confirmed):** `reset: moving to HEAD` executed at 23:47:14
7. **Event 7 (Inferred):** Working tree reset, uncommitted `plan.yaml` changes discarded
8. **Event 8 (Confirmed):** Commits resume at 23:47:26, 23:48:40, 23:49:51
9. **Event 9 (Confirmed):** State divergence now exists: `plan.yaml` (stale) vs `state.json` (current)

---

## Causal Chain

```
Subagent working on shared worktree
        ↓
plan.yaml becomes dirty (uncommitted changes)
        ↓
state.json updated at 23:38:24 (preserves progress)
        ↓
plan.yaml modified at 23:39:36 (more uncommitted changes)
        ↓
Commits created 23:39:44 - 23:46:51 (source code changes)
        ↓
[ROOT CAUSE] git reset executed at 23:47:14
        ↓
Working directory reset to HEAD commit (0a7a94fb)
        ↓
Uncommitted plan.yaml changes DISCARDED
        ↓
State divergence created:
  - plan.yaml: reverted to 23:38 state (pre-23:39:36 changes lost)
  - state.json: preserved 23:38:24 state (intact)
        ↓
Current situation: plan and state out of sync
```

---

## Hypotheses Considered

### Hypothesis A: `git reset --hard HEAD` (Most Likely)

**Description:** A subagent or process ran `git reset --hard HEAD`, discarding working tree changes.

**Evidence Supporting:**
- Working tree changes were lost (plan.yaml reverted)
- Pattern consistent with shared worktree subagent operations
- No stash entry found suggesting alternative preservation

**Evidence Against:**
- `.git/logs/HEAD` only records "reset: moving to HEAD", not the specific mode
- Could theoretically be `--mixed` followed by checkout (less likely)

**Confidence:** **Medium** — behavior consistent with `--hard`, but mode not explicitly confirmed

**Conclusion:** **Most plausible** explanation, pending explicit confirmation of `--hard` flag

---

### Hypothesis B: `git reset --mixed HEAD` + `git checkout -- .`

**Description:** Reset moved HEAD pointer, then checkout discarded changes.

**Evidence Supporting:**
- Would produce same observable outcome

**Evidence Against:**
- Overly complex for observed pattern
- No additional checkout entries in reflog

**Confidence:** **Low**

**Conclusion:** **Rejected** — Occam's Razor favors simpler explanation

---

### Hypothesis C: Shared Worktree Subagent Operation (Best Explanation)

**Description:** Subagent working in shared worktree performed git reset as part of workflow.

**Evidence Supporting:**
- Shared worktree environment (multiple subagents, single git repo)
- Pattern of commits, reset, then commits suggests automated/scripted behavior
- Subagent may have intended to reset to specific state but used HEAD

**Evidence Against:**
- Specific subagent identity not recorded
- Intent (accidental vs intentional) unknown

**Confidence:** **Medium-High** for shared worktree context; **Unknown** for specific actor

**Conclusion:** **Best available explanation** — shared worktree + subagent git operation, actor and intent uncertain

---

## What is Known vs Unknown

### Known (Confirmed by Evidence)

| Item | Detail |
|------|--------|
| Reset operation | `reset: moving to HEAD` at 23:47:14 CST |
| Reset target | HEAD commit `0a7a94fb` (no commit change) |
| File mtimes | `state.json`: 23:38:24; `plan.yaml`: 23:39:36 |
| Effect | plan.yaml reverted to earlier state |
| Phase status | `talk-ux-bugs-v3` shows `done` in state.json |
| Step status | `cli-chat-command.extract-agent-options-decorator` shows `done` in state.json |
| Commits | Sequence 7b689fe → 0a7a94fb → [reset] → 9016038 → 3c97fc9 → 6eae350 |

### Unknown (Not Confirmed)

| Item | Question |
|------|----------|
| Reset mode | `--hard`, `--mixed`, or `--soft`? ( `--hard` most likely) |
| Actor | Which specific subagent or process? |
| Intent | Accidental, cleanup, or intentional workflow step? |
| Lost data | What specific plan.yaml content was discarded? |
| Recovery | Has any reconciliation been attempted? |

---

## Recovery Assessment

### High Confidence Recoverable

| Item | Recovery Method | Status |
|------|-----------------|--------|
| Execution state | `.git/vectl/state.json` contains step statuses, claims, evidence | **PRESERVED** |
| Phase completions | Phase statuses recorded in state.json | **PRESERVED** |
| Step evidence | Evidence strings per step in state.json | **PRESERVED** |
| Git commits | All commits remain in reflog | **INTACT** |

### Medium Confidence Recoverable

| Item | Recovery Method | Status |
|------|-----------------|--------|
| Full plan structure | Reconstruct from state.json + git history | **REQUIRES RECONCILIATION** |
| Step dependencies | May need verification against current plan.yaml | **ASSESSMENT NEEDED** |
| Plan.yaml sync | Manual or automated reconciliation with state.json | **REQUIRES ACTION** |

### Potentially Unrecoverable

| Item | Reason | Impact |
|------|--------|--------|
| Exact plan.yaml changes between 23:38:24 and 23:47:14 | Discarded by reset | **LOW-MEDIUM** — vectl stores primary state separately |
| Any non-git-tracked files | Reset scope unclear | **UNKNOWN** — verify with `git status` |

---

## Prevention Recommendations

### Immediate Actions (This Incident)

1. **Reconcile plan.yaml with state.json:**
   - Compare current plan.yaml against state.json phase/step statuses
   - Restore completed status for `talk-ux-bugs-v3` and other done phases

2. **Verify no other files affected:**
   - Check `git status` for any other unexpected changes
   - Review `git stash list` for any stashed work

### Short-term Prevention

1. **Pre-reset Checks:**
   - Check `git status` before any reset operation
   - Warn when plan.yaml is dirty and uncommitted

2. **Subagent Guidance:**
   - Document: "Never use `git reset --hard` in shared worktree"
   - Prefer `git stash` or explicit commit before destructive operations

3. **State Backup:**
   - Consider `cp .git/vectl/state.json .git/vectl/state.json.backup` before risky operations

### Long-term Improvements

1. **vectl Safety Features:**
   - Add divergence detection: warn when plan.yaml and state.json differ significantly
   - Auto-commit plan.yaml before destructive git operations (opt-in)

2. **Automation Guardrails:**
   - Hook `git reset --hard` to check for dirty plan.yaml
   - Block or require confirmation when vectl-managed files are uncommitted

3. **Documentation:**
   - Document recovery procedure for plan/state divergence
   - Create runbook for subagent shared worktree operations

---

## Open Questions

1. **Reset Mode:** Was the reset `--hard`, `--mixed`, or another variant?
2. **Actor Identity:** Which subagent or automation executed the reset?
3. **Intent:** Was the reset intentional cleanup or accidental?
4. **Lost Content:** What specific plan.yaml modifications were discarded?
5. **Recovery Status:** Has any reconciliation work been performed?
6. **Pattern:** Is this part of a broader pattern of subagent git operations?

---

## Appendix: Raw Evidence

### Git Reflog (relevant entries)

```
7b689fe HEAD@{4}: commit: fix(cli): change spawn --lifecycle default from ephemeral to daemon
0a7a94fb HEAD@{3}: commit: feat(cli): add chat command orchestration
0a7a94fb HEAD@{4}: reset: moving to HEAD
9016038 HEAD@{2}: commit: refactor(cli): replace --sandbox/--no-sandbox is_flag...
3c97fc9 HEAD@{1}: commit: test(cli): add chat cleanup integration coverage
6eae350 HEAD@{0}: commit: test(cli): update expected help text...
```

### State.json Step Status (relevant)

```json
{
  "cli-chat-command.extract-agent-options-decorator": {
    "status": "done",
    "claimed_by": "python-engineer",
    "claimed_at": "2026-03-07T15:30:00+00:00"
  },
  "talk-ux-bugs-v3": { "status": "done" }
}
```

### Timeline Confirmation (Unix Timestamps)

| Event | Unix Timestamp | CST Time |
|-------|----------------|----------|
| Commit `7b689fe` | 1772893184 | 2026-03-07 23:39:44 |
| Commit `0a7a94fb` | 1772898411 | 2026-03-07 23:46:51 |
| **Reset** | **1772898434** | **2026-03-07 23:47:14** |
| Commit `9016038` | 1772898446 | 2026-03-07 23:47:26 |
| Commit `3c97fc9` | 1772898520 | 2026-03-07 23:48:40 |
| Commit `6eae350` | 1772898591 | 2026-03-07 23:49:51 |

### File Modification Times (Evidence)

```
.git/vectl/state.json: 2026-03-07 23:38:24 CST
plan.yaml:            2026-03-07 23:39:36 CST
```

---

*Report compiled: 2026-03-08*  
*Evidence current as of: investigation timestamp*

---

## Resolution (Phase 1 Controls)

**Implemented: 2026-03-08**

### Phase 1 Prevention Controls

The following controls were implemented in `vectl-orchestrator.md`:

#### 1. Git Operations Prohibition (Section 0.1)

- Explicit forbid-list at TOP of subagent dispatch contracts
- Mandatory warning preamble in EVERY dispatch:
  ```
  ⚠️ DANGER — GIT OPERATIONS PROHIBITED
  You are in a SHARED WORKTREE. The following git commands are FORBIDDEN:
  - git reset --hard, git reset --mixed, git checkout -- .
  - git clean -fd, git stash --all
  ```
- Forbidden commands: `git reset --hard`, `git reset --mixed`, `git checkout -- .`, `git clean -fd`, `git stash --all`

#### 2. plan.yaml Durability (Section 1.5)

- Orchestrator-owned checkpoint of dirty `plan.yaml` before parallel dispatch
- Uses `vectl_vectl_checkpoint` for serialization
- Logged in `decision_log` with `CHECKPOINT_DIRTY_PLAN`
- Recovery anchor if state corruption occurs

#### 3. Recovery Mode (Section 1.6)

- Detection signals: "Step not found", phase disappearance, state divergence
- **HALT and ESCALATE** - no self-healing
- Reports anomaly details to user for manual verification
- Never continues normal execution on anomaly

### Architectural Boundary Clarified

| Component | Git Management | plan.yaml Durability |
|-----------|---------------|---------------------|
| vectl core | No (state.json survives resets) | No |
| vectl-orchestrator | May checkpoint | **Yes (owner)** |
| vectl-planner | No | No |
| Subagents | **FORBIDDEN** | **FORBIDDEN** |

### Worktree Isolation

- **NOT Phase 1** - requires architecture changes
- Future work: separate worktrees per subagent
- Current approach: prevention controls in shared worktree context

### Commit

```
feat(orchestrator): prevent shared-worktree plan.yaml loss from subagent git operations
- agents/vectl-orchestrator.md: +166 lines
- agents/vectl-planner.md: +3 lines
```
