Skip to content

Stabilize external saved-package publishing - #427

Merged
kentcdodds merged 5 commits into
mainfrom
cursor/fix-package-publish-memory-resets
May 10, 2026
Merged

kentcdodds merged 5 commits into
mainfrom
cursor/fix-package-publish-memory-resets

Conversation

@kentcdodds

@kentcdodds kentcdodds commented May 9, 2026 •

Copy link
Copy Markdown
Owner

Walkthrough

  • Removed a redundant full workspace materialization from package_publish_external_push: the RepoSession DO no longer calls collectWorkspaceFiles() before runRepoChecks, and publish finalization now reuses the source file snapshot that checks collected.
  • Kept that single checks snapshot semantically equivalent to the old publish snapshot by collecting all source files (**/*) while excluding .git internals, so static assets like CSS/SVG/YAML/etc. remain in published KV snapshots.
  • Added bounded internal retry/recovery for transient Durable Object CPU/memory reset signatures in package_publish_external_push. Each retry re-resolves the package source and Artifacts HEAD, uses a fresh transient RepoSession DO name, and returns already_published if the prior attempt committed before the reset surfaced.
  • Added structured warning/Sentry context for every transient reset attempt, including the final exhausted attempt before returning a stable exhausted-retry error.
  • Added tests for retry success, committed-before-retry recovery, exhausted reset attempts, source-file reuse, static file preservation, and RepoSession snapshot handoff.
  • Centralized the repeated capability error-message helper used by execute and external publish handling.

Why this helps

The live failures were consistent with pressure inside the publish/check pipeline rather than package checkout size. The path cloned the external HEAD, read the entire workspace into memory, then runRepoChecks globbed/read the same tree again and built another snapshot for bundle/typecheck work. Reusing the checks snapshot removes one full source-tree read and one long-lived copy from the RepoSession DO request. The retry path covers remaining transient DO resets with idempotent recovery instead of exposing raw platform reset errors to callers.

Remaining work

A deeper async publish redesign may still be warranted if bundle/typecheck plus projection rebuilds continue to approach DO limits for packages with many targets or dependencies. The likely next step would be moving check/projection artifact rebuilds into a Workflow/queue with durable run status rather than keeping all phases in the MCP/RepoSession request path.

Validation

  • npx vitest run --project node-unit packages/worker/src/mcp/capabilities/packages/publish-external-push.node.test.ts packages/worker/src/repo/repo-session-do.node.test.ts packages/worker/src/repo/checks.node.test.ts packages/worker/src/repo/external-publish.node.test.ts packages/worker/src/mcp/capabilities/meta/search-and-execute.node.test.ts — 52 tests passed
  • npm run typecheck — passed
  • npm run format:check — passed
  • npm run lint — passed with existing unrelated warning in packages/worker/src/email/inbound.workers.test.ts
  • Previous latest substantial revision: npm run validate passed (format, lint, typecheck, unit tests, Playwright E2E, and MCP E2E all exited 0). Full PR CI is re-running after the latest feedback fix.
Open in Web Open in Cursor 

Summary by CodeRabbit

  • New Features

    • Added automatic retry for transient external-publish failures with backoff (up to 3 attempts).
  • Improvements

    • Better error tracking and structured logging for publish retries and failures.
    • Publish now accepts optional caller files and will use repository-collected source files when absent.
    • Repository snapshotting now includes non-code assets while excluding git internals.
  • Tests

    • Added tests covering retry behavior, already-published detection, source-file finalization, and snapshot contents.

Review Change Stack

Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
@coderabbitai

coderabbitai Bot commented May 9, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR adds retry logic for transient Durable Object resets in the publish-external-push capability, refactors runRepoChecks to collect and return sourceFiles (snapshotting all repo files except .git), makes publishFromExternalRef accept optional files with a fallback to checks.sourceFiles, and removes local workspace file collection in the repo session path.

Changes

Publish Retry & Source File Propagation

Layer / File(s) Summary
Type Definitions & Contracts
packages/worker/src/repo/checks.ts, packages/worker/src/repo/external-publish.ts
RepoCheckRunResult adds sourceFiles: Record<string,string>; publishFromExternalRef input files is now optional.
Retry Helpers & Error Handling
packages/worker/src/mcp/capabilities/packages/publish-external-push.ts
Adds Sentry import, transient reset detection, normalized error messages, delay schedule [100,500] ms, structured logging with captureException, and an async delay utility.
Publish Capability Retry Loop
packages/worker/src/mcp/capabilities/packages/publish-external-push.ts
Replaces single-shot publish with retry loop: recalculates source/HEAD per attempt, appends sessionId suffix per retry, returns already_published when HEAD matches, retries only on transient durable-object reset errors, throws consolidated error after exhausting attempts.
Source File Collection in Checks
packages/worker/src/repo/checks.ts
runRepoChecks now builds a plain sourceFiles object from a **/* snapshot (excluding .git), removes createSourceFilesRecord, passes sourceFiles into validation, and includes sourceFiles in all return paths.
External Publish Fallback Integration
packages/worker/src/repo/external-publish.ts
When finalizing published entity source, use input.files ?? checks.sourceFiles so checks-provided files are used if caller omitted files.
Session Layer Integration
packages/worker/src/repo/repo-session-do.ts
Remove local collectWorkspaceFiles() in publishFromExternalRef, keep git.checkout(..., force: true), and delegate to publishExternalRefSource without files.
Tests & Assertions
packages/worker/src/mcp/capabilities/packages/publish-external-push.node.test.ts, packages/worker/src/repo/external-publish.node.test.ts, packages/worker/src/repo/repo-session-do.node.test.ts, packages/worker/src/repo/checks.node.test.ts
Mock @sentry/cloudflare.captureException; add tests for transient retry behavior (sessionId increments, console.warn, captureException tag), already_published observed on retry, exhaustion after 3 attempts, finalize-from-checks path, and assert workspaceGlob not called.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • kentcdodds/kody#352: Both PRs modify the external-publish flow in the MCP capability package_publish_external_push and its interaction with repoSessionRpc.publishFromExternalRef.
  • kentcdodds/kody#363: Both PRs modify packages/worker/src/repo/checks.ts and refactor how runRepoChecks produces and returns source file data.
  • kentcdodds/kody#306: Both PRs propagate collected sourceFiles from runRepoChecks to publishFromExternalRef and implement fallback behavior.

Poem

🐰 Hoppy through resets, I try again,

session-IDs grow like springtime rain.
Files gathered safe from root to stem,
Retries counted—three at most for them.
A carrot toast to cleaner publish zen.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Stabilize external saved-package publishing' clearly and specifically summarizes the main change: improving the reliability and reducing redundancy in external package publishing through retry logic and snapshot reuse.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cursor/fix-package-publish-memory-resets

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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 and usage tips.

@kentcdodds
kentcdodds marked this pull request as ready for review May 10, 2026 00:25
@github-actions

github-actions Bot commented May 10, 2026 •

Copy link
Copy Markdown
Contributor

🔎 Preview deployed: https://kody-pr-427.kentcdodds.workers.dev

Worker: kody-pr-427
D1: kody-pr-427-db
KV: kody-pr-427-oauth-kv

Mocks:

Comment thread packages/worker/src/repo/external-publish.ts

@coderabbitai coderabbitai 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/worker/src/repo/repo-session-do.node.test.ts (1)

927-955: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Clear workspaceGlob before asserting zero calls.

mockModule.workspaceGlob is shared across this whole suite, and earlier tests in this file already use it. Without a local mockClear()/mockReset(), this assertion becomes order-dependent and can fail even when this publish path never globbed the workspace.

Proposed fix
 test('publishFromExternalRef checks fast-forward ancestry through shell git adapter', async () => {
 	setCommonSessionFixtures()
+	mockModule.workspaceGlob.mockClear()
 	mockModule.getEntitySourceById.mockResolvedValue({
 		id: 'source-1',
 		user_id: 'user-1',
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/worker/src/repo/repo-session-do.node.test.ts` around lines 927 -
955, In the test 'publishFromExternalRef checks fast-forward ancestry through
shell git adapter' clear the shared mock state for workspaceGlob to make the
assertion order-independent: call mockModule.workspaceGlob.mockClear() (or
mockReset()) before invoking repoSession.publishFromExternalRef (i.e., after
test fixtures and mockModule.git.log setup but before awaiting
repoSession.publishFromExternalRef) so the subsequent
expect(mockModule.workspaceGlob).not.toHaveBeenCalled() only reflects this
test's activity.
packages/worker/src/repo/external-publish.ts (1)

161-166: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

checks.sourceFiles is too narrow for the published snapshot.

Line 165 now falls back to checks.sourceFiles, but runRepoChecks only harvests **/*.{ts,tsx,js,jsx,json} for bundling/typechecking. That means publishes with other source-root assets (.md, .sql, .css, .html, etc.) will now persist an incomplete snapshot, and downstream readers will miss files that were present at the published commit.

Please keep using a publish-grade full snapshot here, or have checks return a separate full file set instead of reusing the bundler-only record.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/worker/src/repo/external-publish.ts` around lines 161 - 166, The
call to finalizePublishedEntitySource is falling back to checks.sourceFiles
(from runRepoChecks), but runRepoChecks only collects bundler/typecheck globs
and omits non-code assets, causing incomplete published snapshots; update the
flow so finalizePublishedEntitySource always receives a publish-grade full file
list: either (A) ensure runRepoChecks returns a new property like fullFiles (or
fullSnapshotFiles) containing the complete repository snapshot and pass that
(use checks.fullFiles instead of checks.sourceFiles), or (B) keep input.files as
the single source of truth for publishing and make the caller populate
input.files with the full snapshot before calling finalizePublishedEntitySource;
locate usages around finalizePublishedEntitySource, runRepoChecks, and
checks.sourceFiles to implement the chosen approach and update types/tests
accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@packages/worker/src/repo/external-publish.ts`:
- Around line 161-166: The call to finalizePublishedEntitySource is falling back
to checks.sourceFiles (from runRepoChecks), but runRepoChecks only collects
bundler/typecheck globs and omits non-code assets, causing incomplete published
snapshots; update the flow so finalizePublishedEntitySource always receives a
publish-grade full file list: either (A) ensure runRepoChecks returns a new
property like fullFiles (or fullSnapshotFiles) containing the complete
repository snapshot and pass that (use checks.fullFiles instead of
checks.sourceFiles), or (B) keep input.files as the single source of truth for
publishing and make the caller populate input.files with the full snapshot
before calling finalizePublishedEntitySource; locate usages around
finalizePublishedEntitySource, runRepoChecks, and checks.sourceFiles to
implement the chosen approach and update types/tests accordingly.

In `@packages/worker/src/repo/repo-session-do.node.test.ts`:
- Around line 927-955: In the test 'publishFromExternalRef checks fast-forward
ancestry through shell git adapter' clear the shared mock state for
workspaceGlob to make the assertion order-independent: call
mockModule.workspaceGlob.mockClear() (or mockReset()) before invoking
repoSession.publishFromExternalRef (i.e., after test fixtures and
mockModule.git.log setup but before awaiting repoSession.publishFromExternalRef)
so the subsequent expect(mockModule.workspaceGlob).not.toHaveBeenCalled() only
reflects this test's activity.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 28a32437-4cee-4ab1-9973-df65ae5cc370

📥 Commits

Reviewing files that changed from the base of the PR and between 74ebfaf and 7c10c12.

📒 Files selected for processing (7)
  • packages/worker/src/mcp/capabilities/packages/publish-external-push.node.test.ts
  • packages/worker/src/mcp/capabilities/packages/publish-external-push.ts
  • packages/worker/src/repo/checks.ts
  • packages/worker/src/repo/external-publish.node.test.ts
  • packages/worker/src/repo/external-publish.ts
  • packages/worker/src/repo/repo-session-do.node.test.ts
  • packages/worker/src/repo/repo-session-do.ts
💤 Files with no reviewable changes (1)
  • packages/worker/src/repo/repo-session-do.ts

Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/worker/src/repo/checks.ts`:
- Around line 160-166: The current glob (pattern) and loop in thisRepo's file
collection collects everything under normalizedRoot (via input.workspace.glob
and files) which can include sensitive or large files; implement an explicit
allow/deny snapshot policy before adding file contents to sourceFiles: parse and
honor repository ignore rules and package.json "files" (if present), and apply a
deny-list for known sensitive/build dirs and file patterns (e.g., node_modules,
.gitignored paths, .env, *.pem, build/, dist/, *.log, artifacts/). Modify the
loop that reads files (the files for..of block using normalizeRepoWorkspacePath
and input.workspace.readFile) to first run each normalizedPath through this
policy and skip any denied paths, and ensure the policy logic is centralized
(e.g., a helper like isSnapshotAllowed(path)) so publish finalization reuses it.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e24b31ba-ab9a-4abd-8c4b-5195efabb354

📥 Commits

Reviewing files that changed from the base of the PR and between 7c10c12 and a39eca2.

📒 Files selected for processing (2)
  • packages/worker/src/repo/checks.node.test.ts
  • packages/worker/src/repo/checks.ts

Comment on lines +160 to 166
const pattern = normalizedRoot === '' ? '**/*' : `${normalizedRoot}/**/*`
const files = await input.workspace.glob(pattern)
for (const file of files) {
if (file.type !== 'file') continue
const normalizedPath = normalizeRepoWorkspacePath(file.path)
if (normalizedPath.split('/').includes('.git')) continue
const content = await input.workspace.readFile(file.path)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Add a publish snapshot allow/deny policy before collecting **/* files.

Line 160 and Line 165 now include every file under sourceRoot except .git. Since this sourceFiles payload is reused for publish finalization, this can unintentionally ship sensitive or high-volume files (for example local secrets/artifacts) and increase reset risk again. Please gate snapshot inclusion with an explicit policy (e.g., honor package files/ignore rules and block known sensitive/build directories).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/worker/src/repo/checks.ts` around lines 160 - 166, The current glob
(pattern) and loop in thisRepo's file collection collects everything under
normalizedRoot (via input.workspace.glob and files) which can include sensitive
or large files; implement an explicit allow/deny snapshot policy before adding
file contents to sourceFiles: parse and honor repository ignore rules and
package.json "files" (if present), and apply a deny-list for known
sensitive/build dirs and file patterns (e.g., node_modules, .gitignored paths,
.env, *.pem, build/, dist/, *.log, artifacts/). Modify the loop that reads files
(the files for..of block using normalizeRepoWorkspacePath and
input.workspace.readFile) to first run each normalizedPath through this policy
and skip any denied paths, and ensure the policy logic is centralized (e.g., a
helper like isSnapshotAllowed(path)) so publish finalization reuses it.

Comment thread packages/worker/src/mcp/capabilities/packages/publish-external-push.ts Outdated
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Comment thread packages/worker/src/repo/repo-session-do.node.test.ts
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>

@cursor cursor 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.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 8d11149. Configure here.

Comment thread packages/worker/src/mcp/capabilities/packages/publish-external-push.ts Outdated
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
@kentcdodds
kentcdodds merged commit 41b232a into main May 10, 2026
5 checks passed
@kody-bot
kody-bot deleted the cursor/fix-package-publish-memory-resets branch July 21, 2026 18:58
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