Skip to content

fix(github): harden Variable + Comment reconcile + add lifecycle convergence tests#226

Open
sam-goodwin wants to merge 1 commit intomainfrom
claude/harden-github-var-comment
Open

fix(github): harden Variable + Comment reconcile + add lifecycle convergence tests#226
sam-goodwin wants to merge 1 commit intomainfrom
claude/harden-github-var-comment

Conversation

@sam-goodwin
Copy link
Copy Markdown
Contributor

Hardens the GitHub Variable and Comment resources, mirroring the per-resource sweep started in #184. Single GitHub scope; Secret is being hardened separately.

Reconciler changes

Variable — tolerate the create race and skip the PATCH on a true no-op so updatedAt doesn't churn:

- yield* Effect.tryPromise(() => octokit.rest.actions.createRepoVariable({ … }));
- return { updatedAt: new Date().toISOString() };
+ const created = yield* Effect.tryPromise({
+   try: async () => {
+     try { await octokit.rest.actions.createRepoVariable({ … }); return true; }
+     catch (e: any) { if (e.status === 422) return false; throw e; }
+   },
+   catch: (e) => e as Error,
+ });
+ if (created) return { updatedAt: new Date().toISOString() };
+ // … fall through to the PATCH path below
- if (observed.value !== news.value) { /* PATCH */ }
- return { updatedAt: new Date().toISOString() };
+ if (observed === undefined || observed.value !== news.value) {
+   /* PATCH */ ; return { updatedAt: new Date().toISOString() };
+ }
+ return { updatedAt: output?.updatedAt ?? new Date().toISOString() };

Comment — only PATCH when the observed body drifts from desired (instead of always issuing the call), so a redeploy with the same body is a true no-op:

- // always PATCH (idempotent on the server, but churns updatedAt)
- const { data } = yield* Effect.tryPromise(() => octokit.rest.issues.updateComment({ … }));
+ if (observed.body !== body) {
+   const { data } = yield* Effect.tryPromise(() => octokit.rest.issues.updateComment({ … }));
+   return { commentId: data.id, htmlUrl: data.html_url, updatedAt: data.updated_at };
+ }
+ return { commentId: observed.id, htmlUrl: observed.htmlUrl, updatedAt: observed.updatedAt };

Both resources gain a diff that triggers replace when the GitHub-side identity changes — (owner, repository, name) for Variable, (owner, repository, issueNumber) for Comment. Previously a name/target change would silently create a second resource and orphan the old one.

diff: Effect.fn(function* ({ news, olds }) {
  if (!isResolved(news)) return undefined;
  if (
    news.owner !== olds.owner ||
    news.repository !== olds.repository ||
    news.name !== olds.name // issueNumber for Comment
  ) {
    return { action: "replace" } as const;
  }
  return undefined;
}),

New lifecycle tests

Each test runs destroy → deploy → … → destroy on a ScratchStack and asserts convergence at every step. Gated on GITHUB_TEST_OWNER / GITHUB_TEST_REPO / GITHUB_TEST_ISSUE_NUMBER (+ GITHUB_TEST_ISSUE_NUMBER_2 for the Comment replace test); skipped otherwise.

Variable:

  • redeploy with same props is a no-op (updatedAt preserved)
  • reconcile resets a value mutated out-of-band via the raw Octokit SDK
  • reconcile re-creates a variable deleted out-of-band
  • changing name triggers replace; old variable is deleted
  • destroying an already-deleted variable is a no-op
  • adopt(true) re-claims a foreign variable

Comment:

  • redeploy with same props is a no-op (commentId and updatedAt preserved)
  • reconcile resets a body mutated out-of-band
  • reconcile re-creates a comment deleted out-of-band (new commentId)
  • changing issueNumber triggers replace
  • destroying an already-deleted comment is a no-op

…ergence tests

- Variable: tolerate 422 race on createRepoVariable, fall through to PATCH
- Variable: skip PATCH on no-op so updatedAt is preserved across redeploys
- Variable: replace on owner/repository/name change
- Comment: only PATCH when observed body drifts from desired
- Comment: replace on owner/repository/issueNumber change
- Add per-resource lifecycle convergence tests gated on GITHUB_TEST_OWNER /
  GITHUB_TEST_REPO / GITHUB_TEST_ISSUE_NUMBER env vars

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@alchemy-version-bot
Copy link
Copy Markdown
Contributor

Install the packages built from this commit:

alchemy

bun add alchemy@https://pkg.ing/alchemy/17c0d50

@alchemy.run/better-auth

bun add @alchemy.run/better-auth@https://pkg.ing/@alchemy.run/better-auth/17c0d50

@alchemy.run/pr-package

bun add @alchemy.run/pr-package@https://pkg.ing/@alchemy.run/pr-package/17c0d50

@alchemy-version-bot
Copy link
Copy Markdown
Contributor

alchemy-version-bot Bot commented May 5, 2026

Website Preview Deployed

URL: https://alchemyeffectwebsite-worker-pr-226-gtuxpn3scert62y3.testing-2b2.workers.dev

Built from commit 17c0d50.


This comment updates automatically with each push.

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