Skip to content

fix(coding-agent): make keybinding migration failure-clean - #3866

Open
kimdogyeom wants to merge 2 commits into
Yeachan-Heo:devfrom
kimdogyeom:fix/keybinding-migration-failure-clean
Open

fix(coding-agent): make keybinding migration failure-clean#3866
kimdogyeom wants to merge 2 commits into
Yeachan-Heo:devfrom
kimdogyeom:fix/keybinding-migration-failure-clean

Conversation

@kimdogyeom

Copy link
Copy Markdown
Contributor

Summary

  • preserve the first legacy keybindings backup with exclusive creation
  • publish migrated primary and migration marker through fsynced temporary files and atomic renames
  • clean owned temporary files on every publication failure and resume marker publication after a committed primary

Verification

  • bun test packages/coding-agent/test/keybindings-config.test.ts packages/coding-agent/test/ux-adversarial.test.ts (12 pass)
  • bun --cwd=packages/coding-agent run check

The synchronous loading API and immediate migrated keybinding usability remain unchanged.

Legacy migration overwrote the first backup and could leave stale temporary files after synchronous publication failures. Atomic fsynced staging now preserves recovery evidence and lets marker publication resume safely after the primary has committed.

Lore-id: a93c6f10
Constraint: preserve the synchronous keybinding loading API and immediate migrated defaults
Constraint: never overwrite the first keybindings.json.bak
Rejected: asynchronous migration | delays immediate keybinding usability and changes lifecycle semantics
Confidence: high
Scope-risk: narrow
Reversibility: easy
Tested: bun test packages/coding-agent/test/keybindings-config.test.ts packages/coding-agent/test/ux-adversarial.test.ts
Tested: bun --cwd=packages/coding-agent run check
Copilot AI lite review requested due to automatic review settings August 5, 2026 10:41
@kimdogyeom

Copy link
Copy Markdown
Contributor Author

Migration invariants and failure rationale

This stays synchronous deliberately. Keybindings are consumed during construction, so moving persistence to an async lifecycle would either delay immediate usability or introduce a second observable configuration state. The work here is about publication durability and atomic visibility, not latency: each primary/marker payload is written to a uniquely owned temporary file, fsynced, closed, and renamed before the next publication stage begins.

The .bak is recovery evidence, not a rolling snapshot. It is created with COPYFILE_EXCL; an existing first backup is authoritative and is never overwritten, including on retries.

The marker is a completion receipt. It is published only after the migrated primary rename succeeds. A marker-stage failure therefore leaves a parseable canonical primary, the original first backup, and no false receipt; the next synchronous load recognizes that the primary is already canonical and retries only atomic marker publication. Once the marker exists, later loads perform no migration writes. The primary is never rolled back merely because receipt publication failed.

Fault injection covers backup plus open/write/fsync/close/rename for both primary and marker. Every injected stage left zero .tmp files; pre-primary failures retained the legacy primary and no marker, marker-stage failures retained the canonical primary and no marker, and every retry converged to the same canonical primary/first backup/v1 receipt.

Observed verification:

  • bun test packages/coding-agent/test/keybindings-config.test.ts packages/coding-agent/test/ux-adversarial.test.ts — 12 pass, 145 expectations
  • bun --cwd=packages/coding-agent run check — Biome and TypeScript passed

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR hardens the coding-agent’s legacy keybindings migration so that it is failure-clean and more atomic, ensuring backups are preserved and partially-written migration artifacts don’t linger.

Changes:

  • Preserve the first legacy backup via exclusive creation (COPYFILE_EXCL) rather than overwriting.
  • Publish migrated primary config and the migration marker via per-run unique temp files, fsync, and atomic rename with cleanup on failures.
  • Expand tests to cover atomic publication, idempotent resume behavior, and temporary-file cleanup.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
packages/coding-agent/src/config/keybindings.ts Refactors keybinding migration to use exclusive backup creation and atomic temp-file publication with cleanup hooks.
packages/coding-agent/test/keybindings-config.test.ts Adds regression tests for atomic migration publication, stage-by-stage failure cleanup, and idempotent resume.
packages/coding-agent/test/ux-adversarial.test.ts Extends adversarial UX probe to assert marker creation and absence of leftover temp files.
packages/coding-agent/CHANGELOG.md Documents the migration hardening under Unreleased fixes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +475 to +486
beforeStage(`${stagePrefix}-open`);
descriptor = fs.openSync(temporary, "wx");
beforeStage(`${stagePrefix}-write`);
fs.writeFileSync(descriptor, content, "utf-8");
beforeStage(`${stagePrefix}-fsync`);
fs.fsyncSync(descriptor);
beforeStage(`${stagePrefix}-close`);
fs.closeSync(descriptor);
descriptor = undefined;
beforeStage(`${stagePrefix}-rename`);
fs.renameSync(temporary, destination);
} finally {

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1c474b3d2a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

const ordered = orderKeybindingsConfig(migratedConfig);
publishKeybindingMigrationFile(filePath, `${JSON.stringify(ordered, null, 2)}\n`, "primary", beforeStage);
}
publishKeybindingMigrationFile(markerPath, "v1\n", "marker", beforeStage);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Don't mark untouched configs as migrated

When keybindings.json already contains only namespaced keys, this still writes .migration-v1 even though migrated is false. If that user later adds or fixes an older unqualified binding, the marker path returns toKeybindingsConfig(rawConfig) without migrateKeybindingNames, and the TUI manager ignores keys that are not in the registry (packages/tui/src/keybindings.ts:224-225), so the documented legacy migration on load (docs/keybindings.md:66) no longer happens for that file. Only publish the marker after an actual migration, or keep translating legacy keys in memory when the marker exists.

Useful? React with 👍 / 👎.

Canonical-only configs incorrectly published the one-time marker, preventing legacy keys added later from being translated. Publish the marker only when legacy names were actually migrated and cover the delayed-legacy path.

Lore-id: 6d45c3a1
Confidence: high
Scope-risk: narrow
Reversibility: easy
Tested: bun test packages/coding-agent/test/keybindings-config.test.ts packages/coding-agent/test/keybindings-migration.test.ts packages/coding-agent/test/ux-adversarial.test.ts
Tested: bun --cwd=packages/coding-agent run check
@kimdogyeom

Copy link
Copy Markdown
Contributor Author

Fixed discussion_r3719925576 in 6ebbbc6. .migration-v1 is now published only when legacy key names were actually migrated, so a canonical-only load cannot suppress translation of legacy keys written later. Added canonical-then-later-legacy regression coverage in the keybinding config and UX adversarial suites, and adjusted marker-publication failure expectations for already-canonical primary files.

Verified:

  • bun test packages/coding-agent/test/keybindings-config.test.ts packages/coding-agent/test/keybindings-migration.test.ts packages/coding-agent/test/ux-adversarial.test.ts (14 pass)
  • bun --cwd=packages/coding-agent run check

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