Skip to content

fix(update): tolerate already-disabled claude plugin and reconcile stale refresh intent - #2561

Merged
namastex888 merged 2 commits into
devfrom
fix/claude-disable-restore-idempotent
Jul 12, 2026
Merged

namastex888 merged 2 commits into
devfrom
fix/claude-disable-restore-idempotent

Conversation

@namastex888

Copy link
Copy Markdown
Contributor

Problem

genie update reports integration refresh: claude — FAILED: ... Plugin "genie@automagik" is already disabled on every run when the Claude plugin is disabled at update time. Reproduced live on v5.260712.1 (stable).

Three coupled defects in src/lib/runtime-integrations.ts:

  1. Non-idempotent restore — the disabled-state restore shells claude plugin disable with allowAlready=false. plugin update never re-enables a disabled plugin, so the target is already disabled, the CLI exits non-zero, and the refresh reports FAILED. (Codex is unaffected — its restore writes config atomically.)
  2. Self-perpetuating loop — the failure keeps ~/.genie/.integration-refresh-claude.json (phase planned, enabled:false), so every subsequent update re-reads it and fails identically.
  3. Stale intent outranks live state — desiredEnabled trusts the leftover file over plugin list; a user who manually re-enables the plugin gets silently re-disabled by the next update, which then reports success.

Fix

  • Pass allowAlready=true on the plugin disable restore call — "already disabled" is the desired end state; the existing plugin list + requireExpectedState verification still confirms it.
  • New reconcilePlannedIntentWithLiveState: a leftover planned-phase intent (previous run settled with the plugin installed) adopts the live enabled state. Later phases (command-started, removal-observed, ambiguous-absent) keep trusting the file — crash recovery semantics unchanged (locked by existing tests).
  • Applied to both convergeClaudePlugin and performCodexPluginConvergence.

Tests

Three new regression tests (all fail on unpatched source, verified):

  • Claude restore tolerates an already-disabled plugin (ok:true, preservedDisabled:true, intent cleared) — previously an untested gap; every existing test returned exit 0 for plugin disable.
  • Claude stale planned intent + live-enabled plugin → no plugin disable issued, plugin stays enabled, intent cleared.
  • Codex parity for the planned-intent reconcile.

Also in this PR

Second commit re-retires the resurrected metrics bot output (README METRICS block + .genie/agents/metrics-updater state, pushed by a scheduled cloud session at 12:12 UTC today after their retirement). That commit broke the release-docs guard test and has held dev CI red since — blocking the release pipeline this fix needs to ship through. The still-live scheduled session needs separate attention or it will re-push.

Validation

  • bun run check exit 0 (typecheck, lint, dead-code, 1399 tests pass / 1 skip).
  • New tests watched failing for the right reason on unpatched source before the fix.

…ale refresh intent

The claude disabled-state restore shelled 'plugin disable' with
allowAlready=false, so an already-disabled plugin (the normal case after
'plugin update') turned the whole integration refresh into FAILED, and
the failure kept the refresh-intent file alive so every later update
failed the same way.

A leftover planned-phase intent also outranked live plugin state: a user
who manually re-enabled the plugin had it silently re-disabled by the
next update. Planned phase means no destructive command is in flight, so
live state is authoritative; later phases keep trusting the file for
crash recovery.

Reproduced live on v5.260712.1 (stable).
A scheduled cloud session re-added the README metrics block and
.genie/agents/metrics-updater state after their retirement, breaking
the release-docs guard test and holding dev CI red.
@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 066541b3-0ef3-4a1f-aebf-518bf1d91917

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/claude-disable-restore-idempotent

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request removes metrics tracking files and the metrics display in the README. It also updates the plugin convergence logic for Claude and Codex to reconcile planned intents with the live state, ensuring that already-disabled plugins do not cause failures. Unit tests have been added to cover these scenarios. The review feedback points out redundant assignments to desiredEnabled in both performCodexPluginConvergence and convergeClaudePlugin that are immediately overwritten later in the functions.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +2292 to +2293
progress.intent = reconcilePlannedIntentWithLiveState(progress.intent, before);
progress.desiredEnabled = progress.intent?.enabled ?? null;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The assignment progress.desiredEnabled = progress.intent?.enabled ?? null; is redundant because progress.desiredEnabled is unconditionally overwritten on line 2307 with progress.desiredEnabled = progress.intent.enabled;. Removing this redundant assignment simplifies the code and avoids dead stores.

Suggested change
progress.intent = reconcilePlannedIntentWithLiveState(progress.intent, before);
progress.desiredEnabled = progress.intent?.enabled ?? null;
progress.intent = reconcilePlannedIntentWithLiveState(progress.intent, before);

Comment on lines +2485 to +2486
intent = reconcilePlannedIntentWithLiveState(intent, before);
desiredEnabled = intent?.enabled ?? null;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The assignment desiredEnabled = intent?.enabled ?? null; is redundant because desiredEnabled is unconditionally overwritten on line 2488 with desiredEnabled = intent.enabled;. Removing this redundant assignment simplifies the code and avoids dead stores.

Suggested change
intent = reconcilePlannedIntentWithLiveState(intent, before);
desiredEnabled = intent?.enabled ?? null;
intent = reconcilePlannedIntentWithLiveState(intent, before);

@namastex888
namastex888 merged commit e84470d into dev Jul 12, 2026
15 checks passed

@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: 6495bde443

ℹ️ 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".

): RefreshIntent | null {
if (intent === null || intent.phase !== 'planned' || !before.installed) return intent;
const liveEnabled = before.enabled === true;
return intent.enabled === liveEnabled ? intent : { ...intent, enabled: liveEnabled };

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 Preserve pending disabled restore across retries

When a run fails during disabled-state restoration after the refresh has left the plugin enabled, the intent file is left as phase: "planned", enabled: false so a later run can retry the restore. This reconciliation now treats that pending retry as stale and rewrites it from the live enabled state, so the next update skips plugin disable/setCodexPluginEnabled, clears the intent, and silently loses the user's disabled consent. This affects both Claude and Codex whenever the first restore attempt fails transiently while the plugin is live-enabled.

Useful? React with 👍 / 👎.

@automagik-genie
automagik-genie deleted the fix/claude-disable-restore-idempotent branch September 25, 2026 04:49
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.

1 participant