Skip to content

Checkpoint skills changes before sync rebase#135

Open
friuns2 wants to merge 2 commits intomainfrom
codex/skills-sync-checkpoint-rebase
Open

Checkpoint skills changes before sync rebase#135
friuns2 wants to merge 2 commits intomainfrom
codex/skills-sync-checkpoint-rebase

Conversation

@friuns2
Copy link
Copy Markdown
Owner

@friuns2 friuns2 commented May 6, 2026

Summary

  • replace skills pull reset/stash reconcile with local checkpoint commits plus rebase
  • push existing ahead checkpoint commits even when no new files are staged
  • surface real git diff failures in committable-change checks

Tests

  • pnpm run build:frontend
  • git diff --check origin/main..HEAD
  • temp Git smoke test: untracked local skill checkpoint rebased on remote update

@qodo-free-for-open-source-projects
Copy link
Copy Markdown

Review Summary by Qodo

Checkpoint skills changes before sync rebase

✨ Enhancement 🐞 Bug fix

Grey Divider

Walkthroughs

Description
• Replace stash-based skills sync with local checkpoint commits
• Implement rebase strategy instead of destructive hard reset
• Surface real git diff failures in committable-change checks
• Push existing ahead checkpoint commits even without new staged files
Diagram
flowchart LR
  A["Local Changes"] -->|"checkpoint commit"| B["Local Checkpoint"]
  B -->|"fetch origin"| C["Remote Updates"]
  C -->|"rebase"| D["Rebased Branch"]
  E["No Staged Changes"] -->|"check HEAD vs origin"| F["Push if Ahead"]
  D --> G["Preserved History"]
  F --> G
Loading

Grey Divider

File Changes

1. src/server/skillsRoutes.ts ✨ Enhancement +28/-46

Replace stash workflow with checkpoint commits and rebase

• Replaced stash push/pop workflow with checkpointLocalSkillsChanges() function that creates local
 commits
• Changed sync strategy from git reset --hard to git rebase for better history preservation
• Removed resolveStashPopConflictsByFileTime() function as stash-based approach is no longer used
• Enhanced hasCommittableWorkingTreeChanges() to return actual diff output instead of relying on
 exit codes
• Added logic to push existing ahead checkpoint commits even when no new staged changes exist

src/server/skillsRoutes.ts


2. tests.md 🧪 Tests +33/-0

Add skills sync checkpoint rebase test documentation

• Added comprehensive test case for skills sync checkpoint behavior
• Documents prerequisites including local tracked edits and untracked skill folders
• Specifies validation steps for checkpoint commit creation and rebase application
• Includes theme switching validation for UI consistency
• Defines expected results showing preserved git history and rebase-based reconciliation

tests.md


Grey Divider

Qodo Logo

@qodo-free-for-open-source-projects
Copy link
Copy Markdown

qodo-free-for-open-source-projects Bot commented May 6, 2026

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider


Action required

1. Commit identity not set 🐞 Bug ☼ Reliability
Description
checkpointLocalSkillsChanges() runs git commit (and ensureSkillsWorkingTreeRepo() runs `git
rebase`) without ensuring user.name/user.email are configured for existing repos, which can
hard-fail sync/pull with “Author identity unknown”. syncInstalledSkillsFolderToRepo() only
configures git identity after calling ensureSkillsWorkingTreeRepo(), so the failure can occur before
the config is applied.
Code

src/server/skillsRoutes.ts[R913-920]

+async function checkpointLocalSkillsChanges(repoDir: string): Promise<void> {
+  await runCommand('git', ['add', '-A'], { cwd: repoDir })
+  try {
+    await runCommand('git', ['diff', '--cached', '--quiet', '--exit-code'], { cwd: repoDir })
+    return
+  } catch {}
+  await runCommand('git', ['commit', '-m', 'Local skills checkpoint before sync'], { cwd: repoDir })
+}
Evidence
ensureSkillsWorkingTreeRepo() calls checkpointLocalSkillsChanges() in the existing-repo path, and
checkpointLocalSkillsChanges() executes git commit. The only local git identity configuration in
this file happens later in syncInstalledSkillsFolderToRepo(), after ensureSkillsWorkingTreeRepo()
has already returned, so the new checkpoint commit (and any rebase that rewrites commits) may run
without an identity.

src/server/skillsRoutes.ts[891-920]
src/server/skillsRoutes.ts[1146-1152]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`ensureSkillsWorkingTreeRepo()` can now create commits (checkpoint commits) and run `git rebase` before the repo has a configured committer identity for existing repos. This can cause sync/pull to fail with an “Author identity unknown” error.
### Issue Context
`syncInstalledSkillsFolderToRepo()` sets `user.email` / `user.name` only after it calls `ensureSkillsWorkingTreeRepo()`. But `ensureSkillsWorkingTreeRepo()` now calls `checkpointLocalSkillsChanges()` (which commits) and then runs a rebase.
### Fix Focus Areas
- src/server/skillsRoutes.ts[867-910]
- src/server/skillsRoutes.ts[913-920]
- src/server/skillsRoutes.ts[1146-1152]
### Suggested fix
- In `ensureSkillsWorkingTreeRepo()` (both the init and existing-repo paths), run:
- `git config user.email skills-sync@local`
- `git config user.name Skills Sync`
before any operation that can create commits (`checkpointLocalSkillsChanges`, `rebase --continue`, merge commits).
- Alternatively (more robust), invoke commit/rebase commands with per-invocation config, e.g. `git -c user.email=... -c user.name=... commit ...` so behavior does not depend on repo/global config.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Conflict resolver drops locals 🐞 Bug ≡ Correctness
Description
localMtimesBeforeSync is only snapshotted when there are uncommitted changes, but the new
rebase-based reconcile can conflict while replaying existing local commits (clean working tree,
commits ahead of origin). When that happens, the resolver treats missing mtimes as 0 and will
consistently prefer the remote side, potentially discarding local committed changes during conflict
auto-resolution.
Code

src/server/skillsRoutes.ts[R905-909]

+  try {
+    await runCommand('git', ['rebase', `origin/${branch}`], { cwd: localDir })
+  } catch {
+    await resolveMergeConflictsByNewerCommit(localDir, branch, localMtimesBeforeSync)
 }
Evidence
The mtime map is empty unless git status --porcelain reports uncommitted changes.
resolveMergeConflictsByNewerCommit() uses localMtimesBeforeSync.get(path) ?? 0, so when the map is
empty, localMtimeSec is 0 and remoteCommitTime will generally be >0, forcing the same side
choice for every conflict. This becomes much more likely to affect real users because this PR
switches reconcile to git rebase origin/ in ensureSkillsWorkingTreeRepo(), which can produce
conflicts even with a clean working tree if local commits exist.

src/server/skillsRoutes.ts[891-909]
src/server/skillsRoutes.ts[934-943]
src/server/skillsRoutes.ts[1104-1116]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The conflict resolver’s decision input (`localMtimesBeforeSync`) is only populated when there are uncommitted changes, but rebase conflicts can occur even when the working tree is clean (e.g., local commits ahead of origin). In that case, the resolver defaults local mtime to 0 and can systematically pick the wrong side, discarding local committed changes.
### Issue Context
- `ensureSkillsWorkingTreeRepo()` now uses `git rebase origin/<branch>` for remote reconcile.
- `resolveMergeConflictsByNewerCommit()` uses `localMtimesBeforeSync.get(path) ?? 0`.
### Fix Focus Areas
- src/server/skillsRoutes.ts[891-909]
- src/server/skillsRoutes.ts[934-943]
- src/server/skillsRoutes.ts[1104-1116]
### Suggested fix
- Compute a meaningful “local recency” signal even when there are no uncommitted changes:
- Option A (simplest): snapshot mtimes unconditionally before running rebase/pull-rebase.
- Option B (more correct for committed changes): when resolving each conflicted `path`, derive local and remote commit times via `git log -1 --format=%ct <ref> -- <path>` for both `HEAD` (or the relevant local side) and `origin/<branch>`, and compare those instead of using an mtime map.
- Apply the same fix in `pushWithNonFastForwardRetry()` since it can also rebase/pull-rebase and invoke the same resolver.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment on lines +913 to +920
async function checkpointLocalSkillsChanges(repoDir: string): Promise<void> {
await runCommand('git', ['add', '-A'], { cwd: repoDir })
try {
await runCommand('git', ['diff', '--cached', '--quiet', '--exit-code'], { cwd: repoDir })
return
} catch {}
await runCommand('git', ['commit', '-m', 'Local skills checkpoint before sync'], { cwd: repoDir })
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. Commit identity not set 🐞 Bug ☼ Reliability

checkpointLocalSkillsChanges() runs git commit (and ensureSkillsWorkingTreeRepo() runs `git
rebase`) without ensuring user.name/user.email are configured for existing repos, which can
hard-fail sync/pull with “Author identity unknown”. syncInstalledSkillsFolderToRepo() only
configures git identity after calling ensureSkillsWorkingTreeRepo(), so the failure can occur before
the config is applied.
Agent Prompt
### Issue description
`ensureSkillsWorkingTreeRepo()` can now create commits (checkpoint commits) and run `git rebase` before the repo has a configured committer identity for existing repos. This can cause sync/pull to fail with an “Author identity unknown” error.

### Issue Context
`syncInstalledSkillsFolderToRepo()` sets `user.email` / `user.name` only after it calls `ensureSkillsWorkingTreeRepo()`. But `ensureSkillsWorkingTreeRepo()` now calls `checkpointLocalSkillsChanges()` (which commits) and then runs a rebase.

### Fix Focus Areas
- src/server/skillsRoutes.ts[867-910]
- src/server/skillsRoutes.ts[913-920]
- src/server/skillsRoutes.ts[1146-1152]

### Suggested fix
- In `ensureSkillsWorkingTreeRepo()` (both the init and existing-repo paths), run:
  - `git config user.email skills-sync@local`
  - `git config user.name Skills Sync`
  before any operation that can create commits (`checkpointLocalSkillsChanges`, `rebase --continue`, merge commits).
- Alternatively (more robust), invoke commit/rebase commands with per-invocation config, e.g. `git -c user.email=... -c user.name=... commit ...` so behavior does not depend on repo/global config.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +905 to 909
try {
await runCommand('git', ['rebase', `origin/${branch}`], { cwd: localDir })
} catch {
await resolveMergeConflictsByNewerCommit(localDir, branch, localMtimesBeforeSync)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

2. Conflict resolver drops locals 🐞 Bug ≡ Correctness

localMtimesBeforeSync is only snapshotted when there are uncommitted changes, but the new
rebase-based reconcile can conflict while replaying existing local commits (clean working tree,
commits ahead of origin). When that happens, the resolver treats missing mtimes as 0 and will
consistently prefer the remote side, potentially discarding local committed changes during conflict
auto-resolution.
Agent Prompt
### Issue description
The conflict resolver’s decision input (`localMtimesBeforeSync`) is only populated when there are uncommitted changes, but rebase conflicts can occur even when the working tree is clean (e.g., local commits ahead of origin). In that case, the resolver defaults local mtime to 0 and can systematically pick the wrong side, discarding local committed changes.

### Issue Context
- `ensureSkillsWorkingTreeRepo()` now uses `git rebase origin/<branch>` for remote reconcile.
- `resolveMergeConflictsByNewerCommit()` uses `localMtimesBeforeSync.get(path) ?? 0`.

### Fix Focus Areas
- src/server/skillsRoutes.ts[891-909]
- src/server/skillsRoutes.ts[934-943]
- src/server/skillsRoutes.ts[1104-1116]

### Suggested fix
- Compute a meaningful “local recency” signal even when there are no uncommitted changes:
  - Option A (simplest): snapshot mtimes unconditionally before running rebase/pull-rebase.
  - Option B (more correct for committed changes): when resolving each conflicted `path`, derive local and remote commit times via `git log -1 --format=%ct <ref> -- <path>` for both `HEAD` (or the relevant local side) and `origin/<branch>`, and compare those instead of using an mtime map.
- Apply the same fix in `pushWithNonFastForwardRetry()` since it can also rebase/pull-rebase and invoke the same resolver.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

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