# Skill Tracker Rebase

> Rebase agent branches onto main, excising rejected PRs. Run the rebase orchestrator, execute the plan per repo (excisions + main rebase), resolve conflicts, and report results.

- Skill: `cnuahs/skill-tracker-rebase` (Agent Skill)
- Install (CLI): `npx skillmds@latest add cnuahs/skill-tracker-rebase`
- Raw SKILL.md: https://api.skillmd.com/api/skills/cnuahs/skill-tracker-rebase/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- License: MIT
- Author: cnuahs (https://skillmd.com/u/cnuahs)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/cnuahs/skill-tracker-rebase

---


# Rebase Agent Branches Onto Main

## Overview

This skill reconciles the agent's branches with upstream `main` after PRs have been reviewed. It excises rejected PRs (closed without merge) from the agent branch, then rebases surviving commits onto the latest `main`.

**Key concepts:** Each tracked skill repo has an *agent branch* where all agent-made commits land. The push+PR cron (Phase 7) cherry-picks commits onto PR branches for human review. After the human merges or closes those PRs, this skill brings the agent branch back into alignment:

- **Merged PRs**: commits are now on `main`; `git rebase main` skips them (content dedup).
- **Rejected PRs** (closed, not merged): commits are excised from the agent branch via `git rebase --onto`.
- **Open PRs**: untouched.

**How it works end-to-end:**

1. **Plan** (scripted) — `rebase.py` reads config, calls `plan_rebase.py` per repo, outputs a JSON plan with excisions and a `rebase_needed` flag.
2. **Execute** (agent) — For each repo: check out agent branch, perform excisions, reset working tree, rebase onto main, reset working tree again.
3. **Resolve conflicts** (agent) — Best-effort, prefer coherent skill over preserving every change.
4. **Report** (agent) — Summarize excisions, conflicts, failures.

## When to Use

- Triggered by the rebase cron job (daily or on webhook)
- When the user asks to "rebase agent branches" or "run the rebase workflow"
- After PRs have been reviewed/merged/rejected on GitHub

## Rules

### Do not modify scripts, skill files, or plugin files
The cron agent must NOT modify any file in `$HERMES_HOME/plugins/skill-tracker/`
or `$HERMES_HOME/skills/skill-tracker/`.  **Do not modify any of these
files.**  They are maintained separately and your changes will be overwritten.

If you encounter what looks like a bug, STOP and REPORT it.  Do not work
around it, patch the script, or improvise a fix.  The correct response is to
abort the run and describe the problem so a human (or a future session with
explicit instructions) can fix it.

The only operations you should perform on the repo are:

- `git fetch` (via the provided scripts)
- `git checkout <agent_branch>` — switch to the agent branch
- `git rebase` / `git rebase --onto` — replay or excise commits
- `git reset --hard HEAD` — reset working tree after rebase
- Conflict resolution (read files, stage, `git rebase --continue`)
- `git diff --quiet HEAD` — dirty-tree guard

### Do not check out `main`
The agent branch (`agent/hermes-gateway`) is the **working branch** — this is
the branch whose commits are replayed.  `main` is the **rebase target** —
the base onto which agent commits are replayed.

**Never `git checkout main`.**  The only `git checkout` in this workflow
is `git checkout <agent_branch>`.  Always.

```bash
# CORRECT: check out the working branch, rebase onto the target
git checkout <agent_branch>
git rebase <main_branch>

# WRONG: this reverses the direction — do NOT do this
git checkout <main_branch>
git rebase <agent_branch>
```

## Prerequisites

- `skill-tracker` plugin is installed and configured in `$HERMES_HOME/config.yaml`
- `skill_tracker.repos` list has at least one valid repo entry
- Each configured repo has a valid `origin` remote (or explicit `url` in config)
- Agent branch exists on each repo (created by the plugin at registration time)
- `rebase.py`, `plan_rebase.py`, and the shared modules (`plugin/gitutils.py`, `plugin/config.py`, `plugin/gh_api.py`, `plugin/queries.py`, `plugin/commitmsg.py`) exist
- `PyYAML` is available in the Python environment (required by `rebase.py` for reading config.yaml)
- `token_env` is configured in `skill_tracker` config and the referenced env var is set (for PR listing via GitHub API)

**Finding the scripts:** The scripts are in the `scripts/` subdirectory of the skill-tracker plugin directory (`$HERMES_HOME/plugins/skill-tracker/scripts/`). All script paths below use `<scripts-dir>` to refer to this location.

**Python environment:** The scripts must run in the Hermes venv. Activate it in each terminal session before running the scripts, e.g.,

```bash
source /opt/hermes/.venv/bin/activate && python3 <scripts-dir>/rebase.py
```

## Workflow

### Step 1: Run the Orchestrator

Run `rebase.py`:

```bash
source /opt/hermes/.venv/bin/activate && python3 <scripts-dir>/rebase.py
```

The script reads `$HERMES_HOME/config.yaml`, iterates over all configured repos, and outputs a JSON array of per-repo rebase plans to stdout.

**Output format** — JSON array of plan objects:

```json
[
  {
    "repo_path": "/opt/data/skills",
    "agent_branch": "agent/hermes-gateway",
    "main_branch": "main",
    "url": "https://github.com/user/skills.git",
    "token_env": "GITHUB_TOKEN",
    "agent_name": "hermes-gateway",
    "excisions": [
      {
        "pr_number": 42,
        "pr_title": "hermes-gateway: consolidate 3 changes to my-skill",
        "onto": "abc123def456...",
        "end": "fed654321cba...",
        "commit_shas": ["abc123...", "def456...", "789abc..."],
        "commit_count": 3
      }
    ],
    "rebase_needed": true
  }
]
```

**Error handling:** If `rebase.py` exits non-zero, log stderr and abort. Do not proceed with partial results.

**Empty result:** If the output is `[]`, report "No repos to rebase" and exit.

### Step 2: Iterate Over Repos

For each repo plan in the JSON output, perform Steps 3–5. Each repo is independent — a failure in one repo must not block others.

Extract these fields from each plan object:
- `repo_path` — where to run git commands
- `agent_branch` — the agent's branch to rebase
- `main_branch` — the stable branch to rebase onto
- `excisions` — list of excision steps
- `rebase_needed` — whether to run `git rebase main` after excisions

### Step 3: Check Out Agent Branch

Before switching branches, check for uncommitted changes on the current
working tree:

```bash
cd <repo_path>
git diff --quiet HEAD || { echo "SKIP: dirty working tree"; exit 1; }
git checkout <agent_branch>
```

If dirty, skip this repo — self-improvement or the curator may be
mid-operation. Do NOT reset or commit the changes. Continue with the
next repo and report the failure in Step 6.

This guards against race conditions (e.g. self-improvement running concurrently).

### Step 4: Perform Excisions

For each excision in the `excisions` list — **in the order returned** (newest
first) — extract the fields below and run the rebase command.

Fields per excision:
- `onto`: SHA of the commit just before the rejected group — rebase target
- `end`: SHA of the latest commit in the rejected group — excision boundary
  (everything from `end` back to, but not including, `onto` is excised)
- `pr_number`: PR number (for logging)
- `commit_shas`: SHAs of the commits being excised (for logging)

```bash
cd <repo_path>
git rebase --onto <onto> <end> <agent_branch>
```

This replays commits that come *after* the rejected group onto the group's
parent, effectively removing the rejected commits from the branch.

**Order matters** — excising an earlier group changes the SHAs of all later
commits on the branch (git replays them). By excising newest-first, each
excision only affects commits that have already been processed.

**Conflicts during excision are EXPECTED.** Excising commits can produce
conflicts if later commits touch files modified by the commits being excised.
In these cases git cannot apply the patch cleanly and reports a conflict.
This is normal git behavior — not a bug, not a sign that something is wrong
with the repo. You MUST resolve the conflict and continue with the rebase
before moving on to the next excision in the list.

**Conflict handling during excision:** If `git rebase --onto` reports conflicts:

1. Examine the conflicted files with `git diff` and `read_file`.
2. Understand the skill's purpose from SKILL.md and surrounding context.
3. Resolve in favor of a coherent, working skill. For conflicts arising during
   excision, the goal is to preserve the *intent* of the commit(s) being replayed
   onto the new base (`onto`).
4. If unsure, prefer dropping the conflicting change over producing a broken skill.
5. Stage resolved files and continue: `git rebase --continue`.

After all excisions for this repo are complete, verify the working tree is clean:

```bash
cd <repo_path>
git diff --quiet HEAD || { echo "WARNING: dirty working tree after excisions — skipping repo"; exit 1; }
```

If the working tree is dirty, files from rejected commits may linger on
disk. Report the issue and move on to the next repo.

### Step 5: Rebase Onto Main

If `rebase_needed` is `true`:

Fetch main (script handles token injection) - updates both remote tracking ref and
local ref:

```bash
source /opt/hermes/.venv/bin/activate && python3 <scripts-dir>/fetch.py <repo_path> <main_branch> \
  --url <url> \
  --token-env <token_env>
```

Rebase the agent branch onto local main (now up to date via fetch.py):

```bash
cd <repo_path>
git checkout <agent_branch>
git rebase <main_branch>
```

Git automatically skips commits whose content already exists on main (merged PRs). The local <main_branch> ref is kept up to date by fetch.py (which calls fetch_branch() to fetch from the remote and then updates the local ref via ``git update-ref``), so it is safe to use directly.

**Conflict handling:** If `git rebase` reports conflicts, examine the
conflicted files, understand the skill's purpose from SKILL.md and surrounding
context, resolve in favor of coherence, then continue:

```bash
cd <repo_path>
git rebase --continue
```

After the main rebase, verify the working tree is clean:

```bash
cd <repo_path>
git diff --quiet HEAD || { echo "WARNING: dirty working tree after rebase — skipping repo"; exit 1; }
```

Same reasoning as Step 4 — ensure working tree consistency. If dirty,
report and move on to the next repo.

### Step 6: Report Results

After processing all repos, report:

```
Rebase run complete.

Repos processed:
- <repo_path>: <N> excisions, <M> conflicts resolved, rebased onto main
- <repo_path>: no excisions, rebased onto main
- <repo_path>: <N> excisions, no rebase needed (rebase_needed=false)

Failed:
- <repo_path>: <error reason>
- <repo_path>: dirty working tree (self-improvement or curator may be running)
```

If no repos needed any work (no excisions, no rebase_needed), report: "All agent branches are up to date. Nothing to rebase."

## Common Pitfalls

### Modifying scripts instead of reporting bugs
The cron agent must NOT modify any scripts or skill files.  If the agent
patches `fetch_branch()` or rewrites `push.py` instead of reporting the
issue, the fix is fragile, will be overwritten, and masks the real problem.
**Action:** stop and describe the problem.  Do not work around it.

### Checking out main instead of the agent branch
`main` is the rebase target — never check it out.  The only `git checkout`
is `git checkout <agent_branch>`.  If you find yourself on `main`, you have
gone wrong.  Abort the rebase, check out the agent branch, and retry.

### Token not available in subprocess
The configured `token_env` variable is available in the agent process environment. The scripts read it from `os.environ`. If testing via `execute_code`, the token won't be available — use the terminal tool instead.

### Partial failures
If some repos succeed and others fail, report both. Do not abort the entire run because one repo failed.

### GitHub API unreachable
If `plan_rebase.py` cannot reach the GitHub API for a repo, it logs a warning and returns an empty PR list (no excisions). The rebase will still fetch main and rebase surviving commits — it just won't excise anything. This is safe.

### Missing PyYAML
`rebase.py` imports `yaml` (PyYAML) to read `config.yaml`. If not installed, the script will fail with `ModuleNotFoundError`. **Action:** stop the run and report the missing dependency to the user. Do not attempt to install it yourself.

### Token env var empty or insufficient
If the configured `token_env` variable is empty, unset, or has insufficient GitHub API permissions, PR listing silently fails and no excisions are planned. The rebase still fetches main and rebases surviving commits. **Action:** if the token is empty or unset, stop the run and report to the user. If the token is set but API calls fail, `plan_rebase.py` logs a warning — check stderr and report the auth error.

### Agent invents false explanations for conflicts
When `git rebase --onto` reports a conflict, it is a REAL content conflict. Do NOT claim files were "moved" or there is a "directory structure mismatch" unless `git log` or `git diff` explicitly shows it. Conflicts during excision are expected. Resolve them normally and continue.

## Verification Checklist

- [ ] `rebase.py` completed successfully (exit code 0)
- [ ] JSON output parsed correctly
- [ ] All repos from the JSON output were processed
- [ ] Agent branch checked out for each repo
- [ ] All excisions performed (or skipped with reason)
- [ ] Working tree clean after excisions (or repo skipped)
- [ ] `git rebase main` performed if `rebase_needed` is true
- [ ] Working tree clean after main rebase (or repo skipped)
- [ ] Conflicts resolved (if any) with coherent skill content
- [ ] Results reported (excisions, conflicts, failures)

