Back to skills
SkillHub ClubShip Full StackFull Stack

unfuck-my-git-state

Diagnose and recover broken Git state and worktree metadata with a staged, low-risk recovery flow. Use when Git reports detached or contradictory HEAD state, phantom worktree locks, orphaned worktree entries, missing refs, 0000000000000000000000000000000000000000 hashes, or branch operations fail with errors like already checked out, unknown revision, not a valid object name, or cannot lock ref.

Packaged view

This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.

Stars
3,077
Hot score
99
Updated
March 20, 2026
Overall rating
C4.0
Composite score
4.0
Best-practice grade
B73.6

Install command

npx @skill-hub/cli install openclaw-skills-unfuck-my-git-state

Repository

openclaw/skills

Skill path: skills/delorenj/unfuck-my-git-state

Diagnose and recover broken Git state and worktree metadata with a staged, low-risk recovery flow. Use when Git reports detached or contradictory HEAD state, phantom worktree locks, orphaned worktree entries, missing refs, 0000000000000000000000000000000000000000 hashes, or branch operations fail with errors like already checked out, unknown revision, not a valid object name, or cannot lock ref.

Open repository

Best for

Primary workflow: Ship Full Stack.

Technical facets: Full Stack.

Target audience: everyone.

License: Unknown.

Original source

Catalog source: SkillHub Club.

Repository owner: openclaw.

This is still a mirrored public skill entry. Review the repository before installing into production workflows.

What it helps with

  • Install unfuck-my-git-state into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
  • Review https://github.com/openclaw/skills before adding unfuck-my-git-state to shared team environments
  • Use unfuck-my-git-state for development workflows

Works across

Claude CodeCodex CLIGemini CLIOpenCode

Favorites: 0.

Sub-skills: 0.

Aggregator: No.

Original source / Raw SKILL.md

---
name: unfuck-my-git-state
description: Diagnose and recover broken Git state and worktree metadata with a staged, low-risk recovery flow. Use when Git reports detached or contradictory HEAD state, phantom worktree locks, orphaned worktree entries, missing refs, 0000000000000000000000000000000000000000 hashes, or branch operations fail with errors like already checked out, unknown revision, not a valid object name, or cannot lock ref.
---

# unfuck-my-git-state

Recover a repo without making the blast radius worse.

## Core Rules

1. Snapshot first. Do not "just try stuff."
2. Prefer non-destructive fixes before force operations.
3. Treat `.git/` as production data until backup is taken.
4. Use `git symbolic-ref` before manually editing `.git/HEAD`.
5. After each fix, run verification before proceeding.

## Fast Workflow

1. Capture diagnostics:
```bash
bash scripts/snapshot_git_state.sh .
```
2. Route by symptom using `references/symptom-map.md`.
3. Generate non-destructive command plan:
```bash
bash scripts/guided_repair_plan.sh --repo .
```
4. Apply the smallest matching playbook.
5. Run `references/recovery-checklist.md` verification gate.
6. Escalate only if the gate fails.

For explicit routing:
```bash
bash scripts/guided_repair_plan.sh --list
bash scripts/guided_repair_plan.sh --symptom phantom-branch-lock
```

## Regression Harness

Use disposable simulation tests before changing script logic:

```bash
bash scripts/regression_harness.sh
```

Run one scenario:

```bash
bash scripts/regression_harness.sh --scenario orphaned-worktree
```

## Playbook A: Orphaned Worktree Metadata

Symptoms:
- `git worktree list` shows a path that no longer exists.
- Worktree entries include invalid or zero hashes.

Steps:
```bash
git worktree list --porcelain
git worktree prune -v
git worktree list --porcelain
```
If stale entries remain, back up `.git/` and remove the specific stale folder under `.git/worktrees/<name>`, then rerun prune.

## Playbook B: Phantom Branch Lock

Symptoms:
- `git branch -d` or `git branch -D` fails with "already used by worktree".
- `git worktree list` seems to disagree with branch ownership.

Steps:
```bash
git worktree list --porcelain
```
Find the worktree using that branch, switch that worktree to another branch or detach HEAD there, then retry the branch operation in the main repo.

## Playbook C: Detached or Contradictory HEAD

Symptoms:
- `git status` says detached HEAD unexpectedly.
- `git branch --show-current` and `git symbolic-ref -q HEAD` disagree.

Steps:
```bash
git symbolic-ref -q HEAD || true
git reflog --date=iso -n 20
git switch <known-good-branch>
```
If branch context is unknown, create a rescue branch from current commit:
```bash
git switch -c rescue/$(date +%Y%m%d-%H%M%S)
```
Then reconnect to the intended branch after investigation.

## Playbook D: Missing or Broken Refs

Symptoms:
- `unknown revision`, `not a valid object name`, or `cannot lock ref`.

Steps:
```bash
git fetch --all --prune
git show-ref --verify refs/remotes/origin/<branch>
git branch -f <branch> origin/<branch>
git switch <branch>
```
Use `reflog` to recover local-only commits before forcing branch pointers.

## Last Resort: Manual HEAD Repair

Only after backup of `.git/`.

Preferred:
```bash
git show-ref --verify refs/heads/<branch>
git symbolic-ref HEAD refs/heads/<branch>
```
Fallback when `symbolic-ref` cannot be used:
```bash
echo "ref: refs/heads/<branch>" > .git/HEAD
```
Immediately run the verification gate.

## Verification Gate (Must Pass)

Run checks in `references/recovery-checklist.md`. Minimum bar:
- `git status` exits cleanly with no fatal errors.
- `git symbolic-ref -q HEAD` matches intended branch.
- `git worktree list --porcelain` has no missing paths and no zero hashes.
- `git fsck --no-reflogs --full` has no new critical errors.

## Escalation Path

1. Archive `.git`:
```bash
tar -czf git-metadata-backup-$(date +%Y%m%d-%H%M%S).tar.gz .git
```
2. Clone fresh from remote.
3. Recover unpushed work with reflog and cherry-pick from old clone.
4. Document failure mode and add guardrails to automation.

## Automation Hooks

When building worktree tooling (iMi, scripts, bots), enforce:
- preflight snapshot and state validation
- post-operation verification gate
- hard stop on HEAD/ref inconsistency
- explicit user confirmation before destructive commands

## Resources

- Symptom router: `references/symptom-map.md`
- Verification checklist: `references/recovery-checklist.md`
- Diagnostic snapshot script: `scripts/snapshot_git_state.sh`
- Guided plan generator: `scripts/guided_repair_plan.sh`
- Disposable regression harness: `scripts/regression_harness.sh`


---

## Referenced Files

> The following files are referenced in this skill and included for context.

### scripts/snapshot_git_state.sh

```bash
#!/usr/bin/env bash
set -euo pipefail

TARGET="${1:-.}"

if ! git -C "$TARGET" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
  echo "error: '$TARGET' is not inside a Git work tree" >&2
  exit 1
fi

TOPLEVEL="$(git -C "$TARGET" rev-parse --show-toplevel)"
TOPLEVEL="$(cd "$TOPLEVEL" && pwd)"

GIT_DIR="$(git -C "$TARGET" rev-parse --git-dir)"
if [[ "$GIT_DIR" = /* ]]; then
  GIT_DIR_ABS="$GIT_DIR"
else
  GIT_DIR_ABS="$TOPLEVEL/$GIT_DIR"
fi

STAMP="$(date +%Y%m%d-%H%M%S)"
OUT_DIR="$TOPLEVEL/.git-state-snapshots/$STAMP"
mkdir -p "$OUT_DIR"

run_capture() {
  local name="$1"
  shift
  local out="$OUT_DIR/$name.txt"
  {
    echo "# $name"
    echo "# command: $*"
    echo
    "$@"
  } >"$out" 2>&1 || true
}

{
  echo "snapshot_time=$STAMP"
  echo "target=$TARGET"
  echo "toplevel=$TOPLEVEL"
  echo "git_dir=$GIT_DIR_ABS"
  echo "git_version=$(git --version)"
} >"$OUT_DIR/context.txt"

if [[ -f "$GIT_DIR_ABS/HEAD" ]]; then
  cat "$GIT_DIR_ABS/HEAD" >"$OUT_DIR/head-file.txt" 2>/dev/null || true
fi

if [[ -d "$GIT_DIR_ABS/worktrees" ]]; then
  ls -la "$GIT_DIR_ABS/worktrees" >"$OUT_DIR/worktrees-dir-listing.txt" 2>/dev/null || true
fi

run_capture status git -C "$TARGET" status --porcelain=v2 --branch
run_capture branch_current git -C "$TARGET" branch --show-current
run_capture symbolic_ref_head git -C "$TARGET" symbolic-ref -q HEAD
run_capture worktree_list git -C "$TARGET" worktree list --porcelain
run_capture branch_all_verbose git -C "$TARGET" branch -vv --all
run_capture remote_verbose git -C "$TARGET" remote -v
run_capture show_ref git -C "$TARGET" show-ref --head
run_capture reflog_head git -C "$TARGET" reflog --date=iso -n 50 HEAD
run_capture fsck git -C "$TARGET" fsck --full --no-reflogs

cat <<EOF
Git state snapshot captured.
Directory: $OUT_DIR
Use these files to diagnose before changing refs or worktrees.
EOF

```

### scripts/guided_repair_plan.sh

```bash
#!/usr/bin/env bash
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

usage() {
  cat <<'EOF'
Usage:
  guided_repair_plan.sh --list
  guided_repair_plan.sh --symptom <symptom-key>
  guided_repair_plan.sh --repo <path-to-repo>
  guided_repair_plan.sh --snapshot <path-to-snapshot-dir>

Description:
  Print non-destructive recovery plans based on a known symptom or snapshot data.
  This script does not run fix commands; it only recommends them.

Symptom keys:
  orphaned-worktree-metadata
  phantom-branch-lock
  detached-head-state
  head-ref-disagreement
  missing-or-broken-refs
  zero-hash-worktree-entry
EOF
}

list_symptoms() {
  cat <<'EOF'
Available symptom keys:
  orphaned-worktree-metadata
  phantom-branch-lock
  detached-head-state
  head-ref-disagreement
  missing-or-broken-refs
  zero-hash-worktree-entry
EOF
}

capture_to_value() {
  local file="$1"
  if [[ ! -f "$file" ]]; then
    return 0
  fi
  grep -v '^#' "$file" | sed '/^[[:space:]]*$/d' | tail -n 1 || true
}

print_plan() {
  local symptom="$1"
  case "$symptom" in
    orphaned-worktree-metadata)
      cat <<'EOF'
[orphaned-worktree-metadata]
Run these first:
  git worktree list --porcelain
  git worktree prune -v
  git worktree list --porcelain

If stale entries still exist, back up `.git/` and then remove only the stale
`.git/worktrees/<name>` directory before rerunning prune.
EOF
      ;;
    phantom-branch-lock)
      cat <<'EOF'
[phantom-branch-lock]
Run these first:
  git worktree list --porcelain

Then:
  1) Identify the worktree currently owning the branch.
  2) In that worktree, switch to another branch (or intentionally detach HEAD).
  3) Retry branch delete/switch in your main repo.

If ownership metadata is stale after verification, back up `.git/` before manual cleanup.
EOF
      ;;
    detached-head-state)
      cat <<'EOF'
[detached-head-state]
Run these first:
  git symbolic-ref -q HEAD || true
  git reflog --date=iso -n 20
  git switch <known-good-branch>

If branch context is unknown:
  git switch -c rescue/$(date +%Y%m%d-%H%M%S)
EOF
      ;;
    head-ref-disagreement)
      cat <<'EOF'
[head-ref-disagreement]
Run these first:
  git branch --show-current
  git symbolic-ref -q HEAD
  git show-ref --verify refs/heads/<expected-branch>
  git symbolic-ref HEAD refs/heads/<expected-branch>

Fallback only after backup:
  echo "ref: refs/heads/<expected-branch>" > .git/HEAD
EOF
      ;;
    missing-or-broken-refs)
      cat <<'EOF'
[missing-or-broken-refs]
Run these first:
  git fetch --all --prune
  git show-ref --verify refs/remotes/origin/<branch>
  git branch -f <branch> origin/<branch>
  git switch <branch>

Before forcing branch pointers, inspect reflog for local-only commits:
  git reflog --date=iso -n 50 HEAD
EOF
      ;;
    zero-hash-worktree-entry)
      cat <<'EOF'
[zero-hash-worktree-entry]
Run these first:
  git worktree list --porcelain
  git worktree prune -v
  git worktree list --porcelain

If zero-hash entries persist, recreate affected worktree(s) from a verified branch ref.
EOF
      ;;
    *)
      echo "error: unknown symptom '$symptom'" >&2
      exit 1
      ;;
  esac
}

run_detection() {
  local snapshot_dir="$1"
  local worktree_file="$snapshot_dir/worktree_list.txt"
  local status_file="$snapshot_dir/status.txt"
  local branch_file="$snapshot_dir/branch_current.txt"
  local symbolic_file="$snapshot_dir/symbolic_ref_head.txt"
  local show_ref_file="$snapshot_dir/show_ref.txt"

  declare -a matches=()

  if [[ -f "$worktree_file" ]]; then
    local orphaned=0
    while IFS= read -r line; do
      local wt_path="${line#worktree }"
      if [[ ! -e "$wt_path" ]]; then
        orphaned=1
        break
      fi
    done < <(grep '^worktree ' "$worktree_file" || true)
    if [[ "$orphaned" -eq 1 ]]; then
      matches+=("orphaned-worktree-metadata")
    fi

    if grep -Eq '^HEAD[[:space:]]+0{40}$' "$worktree_file"; then
      # Unborn branches can legitimately show all-zero HEADs; avoid false positives.
      if [[ -f "$status_file" ]] && grep -Eq 'No commits yet|branch\.oid[[:space:]]+\(initial\)' "$status_file"; then
        :
      else
        matches+=("zero-hash-worktree-entry")
      fi
    fi
  fi

  if [[ -f "$status_file" ]] && grep -Eiq 'detached|HEAD detached' "$status_file"; then
    matches+=("detached-head-state")
  fi

  local cur_branch=""
  local sym_branch=""
  cur_branch="$(capture_to_value "$branch_file" || true)"
  sym_branch="$(capture_to_value "$symbolic_file" || true)"
  sym_branch="${sym_branch#refs/heads/}"
  if [[ -n "$cur_branch" && -n "$sym_branch" && "$cur_branch" != "$sym_branch" ]]; then
    matches+=("head-ref-disagreement")
  fi

  if [[ -f "$status_file" ]] && grep -Eiq 'unknown revision|not a valid object name|cannot lock ref|fatal:' "$status_file"; then
    matches+=("missing-or-broken-refs")
  fi
  if [[ -f "$show_ref_file" ]] && grep -Eiq 'not a valid|fatal:' "$show_ref_file"; then
    matches+=("missing-or-broken-refs")
  fi

  declare -A seen=()
  declare -a unique=()
  local symptom
  for symptom in "${matches[@]}"; do
    if [[ -z "${seen[$symptom]:-}" ]]; then
      unique+=("$symptom")
      seen[$symptom]=1
    fi
  done

  if [[ "${#unique[@]}" -eq 0 ]]; then
    echo "No deterministic symptom match found in snapshot: $snapshot_dir"
    echo "Use --symptom with one of:"
    list_symptoms
    return 0
  fi

  echo "Detected symptom(s) from snapshot: $snapshot_dir"
  for symptom in "${unique[@]}"; do
    echo
    print_plan "$symptom"
  done
}

resolve_snapshot_from_repo() {
  local repo="$1"
  local output
  output="$(bash "$SCRIPT_DIR/snapshot_git_state.sh" "$repo")"
  printf '%s\n' "$output" >&2

  local parsed
  parsed="$(printf '%s\n' "$output" | sed -n 's/^Directory: //p' | tail -n 1)"
  if [[ -n "$parsed" && -d "$parsed" ]]; then
    printf '%s\n' "$parsed"
    return 0
  fi

  local top
  top="$(git -C "$repo" rev-parse --show-toplevel)"
  local latest
  latest="$(ls -1dt "$top/.git-state-snapshots"/* 2>/dev/null | head -n 1 || true)"
  if [[ -n "$latest" && -d "$latest" ]]; then
    printf '%s\n' "$latest"
    return 0
  fi

  echo "error: unable to resolve snapshot directory for repo '$repo'" >&2
  return 1
}

SYMPTOM=""
SNAPSHOT=""
REPO=""
LIST_ONLY=0

while [[ $# -gt 0 ]]; do
  case "$1" in
    --symptom)
      [[ $# -ge 2 ]] || { echo "error: --symptom requires a value" >&2; usage; exit 1; }
      SYMPTOM="$2"
      shift 2
      ;;
    --snapshot)
      [[ $# -ge 2 ]] || { echo "error: --snapshot requires a value" >&2; usage; exit 1; }
      SNAPSHOT="$2"
      shift 2
      ;;
    --repo)
      [[ $# -ge 2 ]] || { echo "error: --repo requires a value" >&2; usage; exit 1; }
      REPO="$2"
      shift 2
      ;;
    --list|-l)
      LIST_ONLY=1
      shift
      ;;
    -h|--help)
      usage
      exit 0
      ;;
    *)
      echo "error: unknown argument '$1'" >&2
      usage
      exit 1
      ;;
  esac
done

if [[ "$LIST_ONLY" -eq 1 ]]; then
  list_symptoms
  exit 0
fi

if [[ -n "$SYMPTOM" ]]; then
  print_plan "$SYMPTOM"
  exit 0
fi

if [[ -z "$SNAPSHOT" && -z "$REPO" ]]; then
  echo "error: provide --symptom, --snapshot, or --repo" >&2
  usage
  exit 1
fi

if [[ -z "$SNAPSHOT" && -n "$REPO" ]]; then
  SNAPSHOT="$(resolve_snapshot_from_repo "$REPO")"
fi

if [[ ! -d "$SNAPSHOT" ]]; then
  echo "error: snapshot directory not found: $SNAPSHOT" >&2
  exit 1
fi

run_detection "$SNAPSHOT"

```

### scripts/regression_harness.sh

```bash
#!/usr/bin/env bash
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
GUIDED_SCRIPT="$SCRIPT_DIR/guided_repair_plan.sh"

if [[ ! -x "$GUIDED_SCRIPT" ]]; then
  echo "error: guided script not executable: $GUIDED_SCRIPT" >&2
  exit 1
fi

WORK_ROOT="$(mktemp -d -t git-state-harness-XXXXXX)"
KEEP_TEMP=0
SINGLE_SCENARIO=""

cleanup() {
  if [[ "$KEEP_TEMP" -eq 1 ]]; then
    echo "Keeping harness workspace: $WORK_ROOT"
    return 0
  fi
  if [[ -d "$WORK_ROOT" ]]; then
    find "$WORK_ROOT" -mindepth 1 -depth -delete 2>/dev/null || true
    rmdir "$WORK_ROOT" 2>/dev/null || true
  fi
}
trap cleanup EXIT

usage() {
  cat <<'EOF'
Usage:
  regression_harness.sh
  regression_harness.sh --scenario <name>
  regression_harness.sh --list
  regression_harness.sh --keep-temp

Scenarios:
  orphaned-worktree
  detached-head
  zero-hash-worktree
  manual-phantom-branch-lock
EOF
}

list_scenarios() {
  cat <<'EOF'
orphaned-worktree
detached-head
zero-hash-worktree
manual-phantom-branch-lock
EOF
}

while [[ $# -gt 0 ]]; do
  case "$1" in
    --scenario)
      [[ $# -ge 2 ]] || { echo "error: --scenario requires a value" >&2; exit 1; }
      SINGLE_SCENARIO="$2"
      shift 2
      ;;
    --keep-temp)
      KEEP_TEMP=1
      shift
      ;;
    --list)
      list_scenarios
      exit 0
      ;;
    -h|--help)
      usage
      exit 0
      ;;
    *)
      echo "error: unknown argument '$1'" >&2
      usage
      exit 1
      ;;
  esac
done

make_repo() {
  local name="$1"
  local repo="$WORK_ROOT/$name"
  mkdir -p "$repo"
  git -C "$repo" init -q
  git -C "$repo" config user.name "Harness Bot"
  git -C "$repo" config user.email "[email protected]"
  echo "seed" >"$repo/seed.txt"
  git -C "$repo" add seed.txt
  git -C "$repo" commit -q -m "seed"
  printf '%s\n' "$repo"
}

assert_contains() {
  local haystack="$1"
  local needle="$2"
  if printf '%s\n' "$haystack" | grep -Fq "$needle"; then
    return 0
  fi
  return 1
}

scenario_orphaned_worktree() {
  local repo wt out
  repo="$(make_repo orphaned-worktree)"
  git -C "$repo" branch repair-me
  wt="$WORK_ROOT/orphaned-worktree-wt"
  git -C "$repo" worktree add -q "$wt" repair-me

  find "$wt" -mindepth 1 -delete
  rmdir "$wt"

  out="$(bash "$GUIDED_SCRIPT" --repo "$repo")"
  assert_contains "$out" "[orphaned-worktree-metadata]" &&
    assert_contains "$out" "git worktree prune -v"
}

scenario_detached_head() {
  local repo out
  repo="$(make_repo detached-head)"
  git -C "$repo" checkout -q --detach
  out="$(bash "$GUIDED_SCRIPT" --repo "$repo")"
  assert_contains "$out" "[detached-head-state]" &&
    assert_contains "$out" "git reflog --date=iso -n 20"
}

scenario_zero_hash_worktree() {
  local repo wt meta out
  repo="$(make_repo zero-hash-worktree)"
  git -C "$repo" branch zero-head
  wt="$WORK_ROOT/zero-hash-worktree-wt"
  git -C "$repo" worktree add -q "$wt" zero-head

  meta="$(find "$repo/.git/worktrees" -mindepth 1 -maxdepth 1 -type d | head -n 1)"
  [[ -n "$meta" ]] || return 1
  printf '0000000000000000000000000000000000000000\n' >"$meta/HEAD"

  out="$(bash "$GUIDED_SCRIPT" --repo "$repo")"
  assert_contains "$out" "[zero-hash-worktree-entry]"
}

scenario_manual_phantom_branch_lock() {
  local out
  out="$(bash "$GUIDED_SCRIPT" --symptom phantom-branch-lock)"
  assert_contains "$out" "[phantom-branch-lock]" &&
    assert_contains "$out" "git worktree list --porcelain"
}

run_scenario() {
  local name="$1"
  case "$name" in
    orphaned-worktree) scenario_orphaned_worktree ;;
    detached-head) scenario_detached_head ;;
    zero-hash-worktree) scenario_zero_hash_worktree ;;
    manual-phantom-branch-lock) scenario_manual_phantom_branch_lock ;;
    *)
      echo "error: unknown scenario '$name'" >&2
      return 1
      ;;
  esac
}

PASS=0
FAIL=0
declare -a SCENARIOS=(
  "orphaned-worktree"
  "detached-head"
  "zero-hash-worktree"
  "manual-phantom-branch-lock"
)

if [[ -n "$SINGLE_SCENARIO" ]]; then
  SCENARIOS=("$SINGLE_SCENARIO")
fi

for scenario in "${SCENARIOS[@]}"; do
  if run_scenario "$scenario"; then
    echo "PASS $scenario"
    PASS=$((PASS + 1))
  else
    echo "FAIL $scenario"
    FAIL=$((FAIL + 1))
  fi
done

echo
echo "Harness result: $PASS passed, $FAIL failed"
if [[ "$FAIL" -ne 0 ]]; then
  exit 1
fi

```

### references/symptom-map.md

```markdown
# Symptom Map

Use this map after running `scripts/snapshot_git_state.sh`.

| Symptom | Evidence to Confirm | Lowest-Risk First Move | Escalation |
| --- | --- | --- | --- |
| Phantom worktree path | `worktree_list.txt` includes a path that does not exist | `git worktree prune -v` | Remove stale `.git/worktrees/<name>` entry after backing up `.git/` |
| Branch "already used by worktree" | Branch delete/switch fails with lock message | Locate holder with `git worktree list --porcelain`; switch branch in that worktree | Remove stale worktree metadata after backup |
| Detached HEAD surprise | `status.txt` says detached and `symbolic_ref_head.txt` is empty | `git reflog`, then `git switch <known-branch>` | Create `rescue/<timestamp>` branch and reattach later |
| HEAD/ref disagreement | `branch_current.txt` does not match symbolic ref expectation | Use `git symbolic-ref HEAD refs/heads/<branch>` | Edit `.git/HEAD` only after backup and failed symbolic-ref |
| Missing object/ref errors | "unknown revision", "not a valid object name", "cannot lock ref" | `git fetch --all --prune` and verify remote ref exists | Force local branch pointer with `git branch -f` after reflog check |
| Zero-hash worktree entries | Worktree list contains all-zero hash values | Prune worktrees and verify filesystem paths | Recreate affected worktree from remote branch |

## Read the Room Before Acting

- If unpushed commits might exist, inspect `reflog_head.txt` before force operations.
- If multiple worktrees exist, fix branch ownership in the worktree that actually holds the branch.
- If refs are broadly broken, stop and back up `.git/` before continuing.

```

### references/recovery-checklist.md

```markdown
# Recovery Checklist

Run this checklist before and after each remediation step.

## Preflight

1. Confirm repo path:
```bash
git rev-parse --show-toplevel
```
2. Capture a snapshot:
```bash
bash scripts/snapshot_git_state.sh .
```
3. If history looks fragile, back up metadata:
```bash
tar -czf git-metadata-backup-$(date +%Y%m%d-%H%M%S).tar.gz .git
```

## Post-Fix Verification Gate

1. Status and branch coherence:
```bash
git status
git branch --show-current
git symbolic-ref -q HEAD
```
2. Worktree integrity:
```bash
git worktree list --porcelain
```
3. Ref health:
```bash
git show-ref --head >/dev/null
git fsck --full --no-reflogs
```
4. Smoke test normal operations:
```bash
git rev-parse HEAD
git log --oneline -n 3
```

## Hard Stop Conditions

Stop and escalate if any of these remain true:

- `git status` prints a fatal error.
- `git symbolic-ref -q HEAD` is empty but detached HEAD is not intentional.
- `git worktree list --porcelain` still references missing paths after prune.
- `git fsck` introduces new critical corruption.

```



---

## Skill Companion Files

> Additional files collected from the skill directory layout.

### README.md

```markdown
# Unfuck My Git State

## A `git reset HEAD --hard` Manifesto

Git is complicated AF — and I say that with love.

I’ve used git almost every day for 24 years, and it still humbles me with its unfathomable complexity. A week still does not go by without me learning one more 'neat trick' or one more way it can literally FUCK my entire day.

I don't care who you are - "oh you're that NASA flight engineer who wrote the navigational algorithm and tweaked the assembly code that landed the Curiosity robot on Mars?" - even you’ve had that moment after git reset --hard where your brain goes: “Did I just vaporize $134M in history… or am I just having a normal day?” Then you run git reflog and its literally more complex than the firehose of black box telemetry data from a Boeing crash.

Git: "You did *what* now? Oh, I gotchu. <prints you a wall of trickling Matrix runes> Hey, you have fun with that buddy!"

You consider the Nuclear Option: Deleting the entire repo on your local machine and re-cloning it. We don’t talk about the *Re-Clone*. It is the developer walk of shame. It is burning down your house because you saw a spider. Oh you've never done that? Fucking liar! We all just want to get back to main

I have this overwhelming sense of empathy when I look at this new wave of vibe coders and I’m like… buddy… you are not emotionally prepared for Git. They arrive thinking ooooh "save button, but spicy!" Then Git throws its first real problem at them and suddenly they’re staring into the abyss, learning what “detached HEAD” means like it’s a medical diagnosis.

That's why I published this new skill, "Unfuck My Git State". It’s basically an airbag for when Git decides to teach you humility.

## What This Is

A recovery skill for broken Git state:
- orphaned worktrees
- phantom "branch already used by worktree" locks
- detached or contradictory `HEAD`
- missing refs and weird object-name failures
- all-zero worktree hashes that scream "something is cursed"

## What This Is Not

- Not a license to spam `reset --hard` until feelings improve.
- Not a replacement for backups.
- Not magic. Just a good process, minus the hand-holding.

## Included

- `SKILL.md`: staged recovery playbooks and escalation flow
- `scripts/snapshot_git_state.sh`: read-only diagnostics snapshot
- `scripts/guided_repair_plan.sh`: non-destructive command planner by symptom
- `scripts/regression_harness.sh`: disposable regression simulator for broken states
- `references/symptom-map.md`: symptom to fix routing
- `references/recovery-checklist.md`: preflight and verification gate

## Quick Start

From the target repo:

```bash
bash /home/delorenj/.agents/skills/unfuck-my-git-state/scripts/snapshot_git_state.sh .
```

Then:
1. Match symptoms in `references/symptom-map.md`.
2. Apply the smallest possible fix.
3. Run the verification checklist before touching anything else.

Quick plan generator:

```bash
bash /home/delorenj/.agents/skills/unfuck-my-git-state/scripts/guided_repair_plan.sh --repo .
```

Regression harness:

```bash
bash /home/delorenj/.agents/skills/unfuck-my-git-state/scripts/regression_harness.sh
```

## Vibe Check

The name is chaotic. The workflow is not.

```

### _meta.json

```json
{
  "owner": "delorenj",
  "slug": "unfuck-my-git-state",
  "displayName": "Unfuck My Git State",
  "latest": {
    "version": "0.2.1",
    "publishedAt": 1770401851808,
    "commit": "https://github.com/openclaw/skills/commit/1b3fdc02ef06e1c35982079cf3c0def4de3cc46e"
  },
  "history": []
}

```