mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
fix(git): reset the dev workspace before a fresh-claim branch checkout
A developer's persistent per-dev clone is shared across tasks, so a finished or abandoned prior task can leave it dirty and on a sibling branch. create_branch's checkouts then fail on the dirty tree — and because this git work runs as a side-effect AFTER the claim's DB transition commits, the task is left marked assigned while the workspace stays on the wrong branch, so the dev's next commit is rejected BRANCH_MISMATCH (stalling then blocking the task). reset --hard the tree before the base/feature checkouts. This runs only on a fresh claim (resume short-circuits in _dev_reentry), so discarded changes are abandoned cruft from a finished task — never commits (reset --hard keeps HEAD), never the gitignored .venv. The branch-preservation test invariant is refined to its real intent: a work-carrying branch must never be RE-POINTED (reset --hard <base>); a bare tree-clean reset is allowed.
This commit is contained in:
@@ -22,6 +22,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
|
||||
- **A gateway verb on a vanished task/agent fails cleanly instead of crashing cryptically.** The verb runner's atomic steps dereference `task.id` / `agent.id` with no guard, so a verb invoked when the task or agent could not be resolved (e.g. a task forced into an unexpected state out-of-band) crashed with an opaque `'NoneType' object has no attribute 'id'`. The runner now fails fast with an actionable `INVALID_STATE` error that tells the agent to re-fetch and re-issue its claim verb.
|
||||
|
||||
- **A dev claiming a new task no longer gets stuck on `BRANCH_MISMATCH`.** Each developer has one persistent clone shared across all their tasks, so a finished or abandoned prior task could leave the clone dirty and sitting on a sibling task's branch. The claim's git work (creating/checking out the new task's branch) runs as a side-effect *after* the claim's DB transition commits — so when the checkout failed on that dirty tree, the task was already marked assigned while the workspace stayed on the wrong branch, and the dev's next commit was rejected with `BRANCH_MISMATCH` (stalling, then blocking, the task). The claim now does a `git reset --hard` to clean the tree before the checkouts. It runs only on a fresh claim (resume short-circuits earlier), so the discarded changes are abandoned cruft from a finished task — never committed work, and never the gitignored `.venv`.
|
||||
|
||||
## [0.10.0] - 2026-06-23
|
||||
|
||||
### Added
|
||||
|
||||
@@ -894,6 +894,19 @@ class GitService(BaseService):
|
||||
timeout=_network_git_timeout(),
|
||||
)
|
||||
|
||||
# The dev workspace is one persistent clone shared across this dev's
|
||||
# tasks, so a finished/abandoned prior task can leave it dirty and on a
|
||||
# sibling branch. Without a clean tree the base + feature checkouts below
|
||||
# fail; and because this git work is a side-effect that runs AFTER the
|
||||
# claim's DB transition has committed, a failed checkout leaves the
|
||||
# workspace on the wrong branch while the task is already marked
|
||||
# assigned — so the dev's next commit is rejected with BRANCH_MISMATCH.
|
||||
# This runs only on a FRESH claim (resume short-circuits in _dev_reentry
|
||||
# before reaching here), so any uncommitted changes are abandoned cruft
|
||||
# from a finished task — safe to discard. `reset --hard` clears tracked
|
||||
# changes; the gitignored .venv (and other ignored files) are untouched.
|
||||
await self._run_git(workspace, ["reset", "--hard"], check=False)
|
||||
|
||||
base_branch = await self._checkout_base_with_fallback(
|
||||
workspace, base_branch, default_branch, task_id
|
||||
)
|
||||
|
||||
@@ -629,8 +629,15 @@ async def test_create_branch_keeps_existing_branch_that_has_work() -> None:
|
||||
calls = await _run_create_branch_with_existing_branch(
|
||||
svc, "feature/frontend/abc12345--def67890", unique_commits="3"
|
||||
)
|
||||
assert not any(c[:2] == ["reset", "--hard"] for c in calls), (
|
||||
"a branch with real work must never be reset"
|
||||
# The fresh-claim tree-clean (a BARE `reset --hard`) is expected — it discards
|
||||
# only uncommitted cruft from a prior task in the shared clone, never commits.
|
||||
assert ["reset", "--hard"] in calls
|
||||
# But the RE-POINT reset (`reset --hard <base>`, which throws commits away)
|
||||
# must NEVER fire for a branch carrying its own work.
|
||||
# `c[2:]` truthy == there is a ref arg after "reset --hard" → it re-points.
|
||||
repoint_resets = [c for c in calls if c[:2] == ["reset", "--hard"] and c[2:]]
|
||||
assert not repoint_resets, (
|
||||
"a branch with real work must never be re-pointed onto base"
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user