Skip to content

fix(harness): migrate legacy cursor install-state into profile sync - #469

Closed
solomonneas wants to merge 1 commit into
mainfrom
fix/issue-468-cursor-install-state-deadlock
Closed

fix(harness): migrate legacy cursor install-state into profile sync#469
solomonneas wants to merge 1 commit into
mainfrom
fix/issue-468-cursor-install-state-deadlock

Conversation

@solomonneas

Copy link
Copy Markdown
Collaborator

Summary

Closes #468.

brigade harness install cursor --scope user (legacy, cursor_user_cmd.py, schema v1) and brigade harness sync --target cursor --scope user (profile layer, harness_profile_cmd.py, schema v2) share ~/.cursor/brigade/install-state.json with incompatible schemas. Each refused the other's state as a conflict with no recovery path — a deadlock whose only manual escape (rm the state file) also destroys the ownership attestations uninstall needs.

Root-cause fix (option 1 from the issue): the legacy installer is already documented as superseded by sync, so the profile layer now detects a legacy version-1 state and deliberately migrates it:

  • harness_profile_cmd.load_profile_state detects {"version": 1, ...} on the cursor harness and adopts every legacy attestation into the v2 ownership model — instruction digest, generated plugin/hook artifacts, the hooks.json#sessionStart entry fingerprint, skill files, and MCP projections (the legacy digest prefix is stable_hash-compatible, so unedited live entries still attest). Adoption is in-memory and read-only until a sync --write persists the migrated state; the result payload reports "migration": {"from": "legacy-install-v1", ...}.
  • The sync then proceeds normally: legacy-owned artifacts reconcile as current, the superseded brigade/mcp.json catalog and legacy-only skill files are removed through the standard removed-profile/removed-registry paths, and nothing user-owned is clobbered.
  • Reverse direction: legacy harness install cursor after a profile sync now fails closed with a conflict that names the recovery step — run brigade harness uninstall --target cursor --scope user from the owning surface first — instead of a bare unsupported ownership state version: 2.

No other harnesses' behavior changes (migration is gated on harness == "cursor" and the legacy v1 shape; non-cursor and malformed states keep the existing fail-closed error).

Tests

  • test_cursor_legacy_install_migrates_into_profile_sync: legacy install → profile sync migrates cleanly (rule survives byte-for-byte, attestations carried), a second sync is a no-op, and harness uninstall --scope user removes exactly the artifacts — originally-legacy and newly-synced alike.
  • test_cursor_profile_sync_then_legacy_install_names_recovery: profile sync → legacy install gives the recovery-named conflict, writes nothing, and leaves the v2 state untouched.
  • test_load_state_migrates_legacy_cursor_install_state_read_only / test_load_state_rejects_non_dict_legacy_cursor_sections: unit coverage for the adoption mapping, read-only guarantee, and fail-closed edges.

All tests drive a temp HOME/tmp_path; the real ~/.cursor is never touched.

Verification (brigade-wired)

brigade work verify run --target . --command "pytest -q tests/test_harness_user_scope_slice2.py tests/test_harness_profile_cmd.py" --capture brigade-work
# status: completed — exit=0 (55 passed)

brigade work verify run --target . --command "./scripts/verify" --capture brigade-work
# status: completed — exit=0 (ruff, ruff format, mypy, version sync; 4186 passed, 3 skipped)

Receipts: .brigade/work/verify-runs/20260724-002316-work-verify-322e6a/ and .brigade/work/verify-runs/20260724-002456-work-verify-019210/.

`brigade harness install cursor` (legacy, v1) and `brigade harness sync
--target cursor` (profile layer, schema v2) share
~/.cursor/brigade/install-state.json with incompatible schemas, and each
refused the other's state with no recovery path.

The profile layer now detects a legacy version-1 state and deliberately
adopts its attestations into the v2 ownership model before syncing, so a
later uninstall still removes exactly what was installed. The legacy
installer now names the recovery step (`brigade harness uninstall
--target cursor --scope user`) when it hits a v2 state, and still fails
closed without writing.

Closes #468

Co-authored-by: Kimi <noreply@moonshot.cn>
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are limited based on label configuration.

🏷️ Required labels (at least one) (1)
  • coderabbit-review

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository: escoffier-labs/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 78c9f157-99ae-489c-8cba-61eff4997832

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/issue-468-cursor-install-state-deadlock

Comment @coderabbitai help to get the list of available commands.

@solomonneas solomonneas self-assigned this Jul 24, 2026
@solomonneas

Copy link
Copy Markdown
Collaborator Author

Superseded by #510, which is the later green fix for the same #468 Cursor install-state migration. Closing this duplicate without merging.

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.

Cursor: legacy harness install and user-profile sync deadlock on shared install-state.json

1 participant