Skip to content

Sync foundation/main (21 commits) - #33

Merged
Adam Poulemanos (bashandbone) merged 23 commits into
mainfrom
sync/foundation-2026-09-22
Sep 22, 2026
Merged

Adam Poulemanos (bashandbone) merged 23 commits into
mainfrom
sync/foundation-2026-09-22

Conversation

@bashandbone

Copy link
Copy Markdown

Upstream 2e37d67f..87e09feb (21 commits), merged per docs/fork-maintenance.md.

Resolution highlights

Fork tooling

  • fork-boundary.json: +35 Tier-1 entries for previously undeclared fork files; reviewedSurvivors pruned (22 stale) + 9 new acks, all false-positive class.
  • Inventory: documented two previously unrecorded divergences (opt-in resources, full-SHA commit capabilities) plus this sync's fallout.

Verification

  • fork:sync --verify: exit 0 (survivors, uncached typecheck, audit clean — no dropped hunks, no format drift, no collisions)
  • pnpm lint: green; full uncached suite on Node 24: 43/43 tasks green

…oudflare#267)

* Load agent catalogs each turn instead of caching them on the chat

A chat cached its agent catalogs and never loaded them again, so the agent could
not see a skill added after the chat opened. A failed load was cached too, as
null, which means "this gatekeeper has no catalog" -- so one failure read as an
empty library for the rest of the chat.

The cache was the cause of both. A catalog states what a session can reach now,
which is not a fact a chat can hold. Loading it per turn removes the stale
window and the cached failure together, and deletes the snapshot type, the
completer, the chat field that stored it, and their tests. A connection blocked
pending a scope-widening restart is still skipped.

The cost is one call per ambient gatekeeper per runAgent invocation. An
automatic compaction reruns the turn, so that turn loads twice.

* Stop asking an ambient connection for a catalog once it answers null

Loading catalogs each turn made every pass call getAgentCatalog() on every
ambient connection, including Scheduler, which answers null on every call. The
Workshop cannot tell which ambient gatekeepers have a catalog: the method is
optional on Gatekeeper, nothing declares it, and over a stub every method looks
callable.

Sharpen the existing null return instead of adding a flag. null now means the
gatekeeper has no catalog, and the Workshop remembers that per connection in
memory for the life of the workspace's Durable Object. A gatekeeper that gains
a catalog in a later version is asked again on the next activation, so every
chat converges. An empty catalog is {entries: []} and is still asked for every
turn. A failed load is not remembered.

Context never returns null and Scheduler always does, so neither changes. The
integration fixture's ambient gatekeeper had no getAgentCatalog at all and took
the failure path on every pass; it now answers null like Scheduler.

* Receiving the catalog is not an observation

The Workshop loads the catalog into every chat's prompt on every turn, so it is
expected and mandatory. Treating it as an observation recorded an action and a
transcript entry per turn, and Context's excludeObservers check could refuse the
whole catalog in a shared workspace once the owner added a private collection.
The catalog is a discovery index of titles and descriptions; it must not hold
anything that needs observer verification, or every workspace would be affected
at once. Reading an item through the session remains an observation.

getAgentCatalog() no longer takes an authorizer, and the Workshop no longer
builds one for the load. Context drops the observation and observer tracking
from its catalog; Scheduler and the integration fixture drop the unused
parameter.
* add deployment-wide user directory for user search

* add admin toggle for controlling user search access

* add in-memory cache for user search admin policy
* Write the bundled format blueprints in TypeScript

A blueprint's files/ may now be TypeScript: client.ts and server.ts are
bundled with their lib/**/*.ts imports into the client.js / server.js the
archive ships, readable rather than minified, so the installed gadget and
the agent that edits it still see one JavaScript file per side. Three
tsconfigs type-check the result under the globals each side really has:
the client under the DOM lib, the server under the Workers types as part
of this package's own program, and the blueprints' own tests under Node's.
A lib/ module is checked under whichever side imports it.
The compaction check ran once, at the start of a turn. A turn whose tool
results grew the prompt past the provider limit then failed mid-turn, the
user saw an error, and compaction only ran after a manual retry.

runAgent now owns the loop the Overseer used to run around it: it loads
the durable history, runs one pass, publishes any checkpoint the pass
produced, and goes again until a pass finishes. A pass ends early either
because it compacted instead of prompting the model (as before) or, new
here, because a persisted tool step left the next request over the
compaction trigger: the step's measured prompt plus its tool results, or,
when the provider reported no usage, a reload regardless so the turn-start
estimator measures the whole prompt. Cancellation, connection requests and
pending approvals still end the turn ahead of compaction.

The 30-step cap on a turn is gone. It came from the ai-sdk's
stepCountIs(30) and was carried over when the loop moved to pi-agent-core;
nothing else needed it.

GPT-5.6 Sol, Luna and Terra gain a `compactionInputBudget` of 272K, the
point past which OpenAI prices their input higher. Compaction sizes
against that instead of the 1.05M window; the window itself is unchanged.
…credential expiry (cloudflare#509)

The Confluence Gatekeeper passed the Atlassian access token's one-hour expiry
to GatekeeperConnectCallback.complete(), reconnectComplete() and
credentialsRestored(). The Workshop treats that date as when the credentials
stop being refreshable, so an hour after connecting (or after the last use)
the account shows "Credentials expired" on the Gatekeepers page and the
connect-resource account picker refuses it until the user runs a full
Reconnect. The credentials are fine: any use refreshes the token from the
rotating refresh token and the label clears for another hour.

The callback contract says not to report a short-lived token-cache expiry
when the gatekeeper refreshes transparently; GitHub and Google report none.
Drop the three arguments and rely on the refresh path plus the explicit
credentialsExpired() signal, which refreshCredentials() already sends on an
auth failure.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Bumps the github-actions group with 2 updates: [voidzero-dev/setup-vp](https://github.com/voidzero-dev/setup-vp) and [actions/upload-artifact](https://github.com/actions/upload-artifact).


Updates `voidzero-dev/setup-vp` from 1.17.0 to 1.19.0
- [Release notes](https://github.com/voidzero-dev/setup-vp/releases)
- [Commits](voidzero-dev/setup-vp@v1.17.0...49c3e4e)

Updates `actions/upload-artifact` from 4.6.2 to 7.0.1
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](actions/upload-artifact@ea165f8...043fb46)

---
updated-dependencies:
- dependency-name: voidzero-dev/setup-vp
  dependency-version: 1.19.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: actions/upload-artifact
  dependency-version: 7.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…flare#501)

Bumps the remaining-npm group with 5 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [isomorphic-git](https://github.com/isomorphic-git/isomorphic-git) | `1.40.5` | `1.41.9` |
| [zod](https://github.com/colinhacks/zod) | `4.4.3` | `4.5.4` |
| [jose](https://github.com/panva/jose) | `6.2.8` | `6.2.12` |
| [diff3](https://github.com/axosoft/diff3) | `0.0.3` | `0.0.4` |
| [dompurify](https://github.com/cure53/DOMPurify) | `3.4.13` | `3.4.15` |



Updates `isomorphic-git` from 1.40.5 to 1.41.9
- [Release notes](https://github.com/isomorphic-git/isomorphic-git/releases)
- [Commits](isomorphic-git/isomorphic-git@v1.40.5...v1.41.9)

Updates `zod` from 4.4.3 to 4.5.4
- [Release notes](https://github.com/colinhacks/zod/releases)
- [Commits](colinhacks/zod@v4.4.3...v4.5.4)

Updates `jose` from 6.2.8 to 6.2.12
- [Release notes](https://github.com/panva/jose/releases)
- [Changelog](https://github.com/panva/jose/blob/main/CHANGELOG.md)
- [Commits](panva/jose@v6.2.8...v6.2.12)

Updates `diff3` from 0.0.3 to 0.0.4
- [Commits](https://github.com/axosoft/diff3/commits)

Updates `dompurify` from 3.4.13 to 3.4.15
- [Release notes](https://github.com/cure53/DOMPurify/releases)
- [Commits](cure53/DOMPurify@3.4.13...3.4.15)

---
updated-dependencies:
- dependency-name: isomorphic-git
  dependency-version: 1.41.9
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: remaining-npm
- dependency-name: zod
  dependency-version: 4.5.4
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: remaining-npm
- dependency-name: jose
  dependency-version: 6.2.12
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: remaining-npm
- dependency-name: diff3
  dependency-version: 0.0.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: remaining-npm
- dependency-name: dompurify
  dependency-version: 3.4.15
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: remaining-npm
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…loudflare#502)

Bumps the editor-codemirror group with 5 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [@codemirror/commands](https://github.com/codemirror/commands) | `6.10.4` | `6.11.0` |
| [@codemirror/state](https://github.com/codemirror/state) | `6.7.1` | `6.7.4` |
| [@codemirror/view](https://github.com/codemirror/view) | `6.43.8` | `6.43.11` |
| [@codemirror/legacy-modes](https://github.com/codemirror/legacy-modes) | `6.5.3` | `6.5.4` |
| [@codemirror/search](https://github.com/codemirror/search) | `6.7.1` | `6.7.2` |



Updates `@codemirror/commands` from 6.10.4 to 6.11.0
- [Changelog](https://github.com/codemirror/commands/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codemirror/commands/commits)

Updates `@codemirror/state` from 6.7.1 to 6.7.4
- [Changelog](https://github.com/codemirror/state/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codemirror/state/commits)

Updates `@codemirror/view` from 6.43.8 to 6.43.11
- [Changelog](https://github.com/codemirror/view/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codemirror/view/commits)

Updates `@codemirror/legacy-modes` from 6.5.3 to 6.5.4
- [Changelog](https://github.com/codemirror/legacy-modes/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codemirror/legacy-modes/commits)

Updates `@codemirror/search` from 6.7.1 to 6.7.2
- [Changelog](https://github.com/codemirror/search/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codemirror/search/commits)

---
updated-dependencies:
- dependency-name: "@codemirror/commands"
  dependency-version: 6.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: editor-codemirror
- dependency-name: "@codemirror/state"
  dependency-version: 6.7.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: editor-codemirror
- dependency-name: "@codemirror/view"
  dependency-version: 6.43.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: editor-codemirror
- dependency-name: "@codemirror/legacy-modes"
  dependency-version: 6.5.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: editor-codemirror
- dependency-name: "@codemirror/search"
  dependency-version: 6.7.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: editor-codemirror
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…lare#515)

Bumps the react-and-ui group with 4 updates in the / directory: [@cloudflare/kumo](https://github.com/cloudflare/kumo/tree/HEAD/packages/kumo), [@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom), [@tanstack/react-router](https://github.com/TanStack/router/tree/HEAD/packages/react-router) and [@tanstack/router-plugin](https://github.com/TanStack/router/tree/HEAD/packages/router-plugin).


Updates `@cloudflare/kumo` from 2.12.0 to 2.13.2
- [Release notes](https://github.com/cloudflare/kumo/releases)
- [Changelog](https://github.com/cloudflare/kumo/blob/main/packages/kumo/CHANGELOG.md)
- [Commits](https://github.com/cloudflare/kumo/commits/@cloudflare/kumo@2.13.2/packages/kumo)

Updates `@types/react-dom` from 19.2.5 to 19.2.7
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom)

Updates `@tanstack/react-router` from 1.170.32 to 1.170.33
- [Release notes](https://github.com/TanStack/router/releases)
- [Changelog](https://github.com/TanStack/router/blob/main/packages/react-router/CHANGELOG.md)
- [Commits](https://github.com/TanStack/router/commits/@tanstack/react-router@1.170.33/packages/react-router)

Updates `@tanstack/router-plugin` from 1.168.35 to 1.168.36
- [Release notes](https://github.com/TanStack/router/releases)
- [Changelog](https://github.com/TanStack/router/blob/main/packages/router-plugin/CHANGELOG.md)
- [Commits](https://github.com/TanStack/router/commits/@tanstack/router-plugin@1.168.36/packages/router-plugin)

---
updated-dependencies:
- dependency-name: "@cloudflare/kumo"
  dependency-version: 2.13.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: react-and-ui
- dependency-name: "@tanstack/react-router"
  dependency-version: 1.170.33
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: react-and-ui
- dependency-name: "@tanstack/router-plugin"
  dependency-version: 1.168.36
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: react-and-ui
- dependency-name: "@types/react-dom"
  dependency-version: 19.2.7
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: react-and-ui
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Measured on the same tasks through the same gateway, two trials each:
Luna finished the suite in 1m35s to GLM 5.3 Flash's 13m20s, used tools
on every task, and had no provider errors. GLM spent the full 32,768
output-token budget on both harder tasks with nothing visible to show
for it, so two of three cohorts were permanently 0% and measured a
model failure rather than the agent. That failure is tracked separately.

Ten trials per task instead of three: at three, one stochastic miss
reads as a 33-point drop. A ten-trial Luna run took 3m35s and $0.40
per arm with no infrastructure failures, and showed project-doc at
8/10 where three trials had read 3/3.

Luna is served only through an AI Gateway with an OpenAI key. A local
run with direct Workers AI credentials now says so and points at
WORKSHOP_EVAL_MODELS; any run that departs from the default model is
told once, up front, that its results are not comparable to published
baselines.
This means now when the agent has mounted a worktree, you can:

* Browse the contents.
* Review changes the agent has made.
* Make your own edits directly, if you want.

Of course, we already had a code editor UI, for gadget code. There are two big things we needed to do to make this work for worktrees:

1. Lazily load files. The code editor had been downloading the entire tree upfront. Now it loads one file at a time, on-demand when you click on it. Obviously, this is needed in order to support large trees.
2. Expand the file chooser sidebar into a full tree view, along with highlighting files with changes at the top:

<img width="340" height="316" alt="Screenshot from 2026-09-15 23-05-27" src="https://github.com/user-attachments/assets/74c59689-a374-49eb-b3ed-f3c63166be38" />
…are#526)

* Frontend: Invite several people at once from the share modal.

With sharing restricted to per-user grants, owners add people in bulk. The
composer now stages recipients as chips (Enter or a directory pick), applies
one role, pipelines the addCollaborator calls and refetches the membership
list once. Unknown accounts and policy errors stay on their chips for
correction instead of being toasted.
…ive data (cloudflare#523)

* Add an owner-invites-only latch for sensitive observations.

A gatekeeper can now mark an observation `ownerInvitesOnly`. Once any such
observation is authorized, the Overseer permanently latches the workspace:
share links can no longer be created, copied, or redeemed by anyone new, and
only the owner can add collaborators directly. People who already joined keep
access and are still re-verified on every open; the owner can still list,
rename, and revoke links.
* Add shared hierarchical list primitive

* Add Kumo hierarchical list adapter

* Add shared frontend conventions

* fix(ui): remove stale Workshop lock entry

* fix(ui): address hierarchical list review findings

* fix(ui): harden hierarchical list interactions

* fix(ui): ignore hierarchical list no-op drops

* fix(ui): resolve hierarchical list interactions

* test(ui): satisfy hierarchical list lint

* fix(ui): harden hierarchical list drag state

* fix(ui): bound hierarchical list actions

* fix(ui): preserve hierarchical list semantics

* fix(ui): close hierarchical list interaction gaps

* fix(ui): use default long-press delay

* fix(ui): compose hierarchical row interactions

* fix(ui): align hierarchical move feedback

* chore: fix lockfile after rebase
…ries (cloudflare#512)

The Workshop aborts an API session on purpose when it loses its
workspace DO (server.ts, #openGadgetInternal) and the browser reconnects
(main.tsx) while the DO resumes the interrupted turn
(#resumeInterruptedAgents). The agent session had no reconnect and only
watched chat events, so after such an abort it sat on the dead socket
for the whole turn budget: both arms of the first eval run lost a
28-minute trial with zero model turns to a turn the product may well
have completed. The session now reconnects like the browser does, with
the chat subscription replaying everything since the session opened,
and the observer finishes a turn from that replay when it never saw
the agent active. Only when no session can be re-established does the
turn fail, with the reason.

Trials in an eval file now run concurrently, each in its own Workshop,
so a file takes as long as its slowest trial rather than the sum of
them. The egress filter moved from per-target to per-file for that:
globalThis.fetch is process-wide. Case titles are built directly so
Vitest no longer truncates the trial number out of them.

Merges to main now measure their own commit and cache it under the same
commit key pull requests use, so the first PR on that base runs one arm
and every relevant main commit gets a score.

Trajectories are rendered to Markdown for the reviewer. Pretty-printed
JSON still put each file the agent wrote on one line, past the 2,000
characters the reviewer's read tool shows; fenced, no line is longer
than its longest source line.
Covers fork-added modules, tests, harnesses and docs that grew without
boundary entries (mcp-shared openapi-publisher, backend helpers, linear
workerd harness, discovery policy, ai-executor fixtures). Undeclared
adds sit outside the survivors scope, so declaring them extends the
removed-name grep to files it previously skipped.
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 22, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-22T16:34:59.468567Z 7fb967a PR opened
🔒 Security Review Completed 2026-09-22T16:35:40.580846Z 7fb967a PR opened
ℹ️ About Codex in GitHub

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

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@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: 7fb967a498

ℹ️ 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 (@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 (@codex) address that feedback".

Comment on lines +3690 to +3693
shouldStopAfterTurn: ({message, toolResults}) => {
// The stop reasons that end the turn come first: a compaction reload must not resume work
// that one of them ended.
if (

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve the per-turn model-step limit across compaction

Restore a turn-wide model-step cap here. When a model continually returns tool calls, this callback now stops only for cancellation, connection/approval waits, or compaction; the previous 30-step guard was removed. Compaction can reload runAgentPass() and continue the same activation, so it does not bound the total number of model calls or side-effecting tools, allowing a degenerate turn to consume substantial inference budget and keep acting until the provider eventually rejects the oversized current turn. Keep the counter outside individual passes so a compaction reload cannot reset it.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good catch on the removed guard, with one scope correction: the old turnCount lived inside runAgent() (base agent.ts:3280), which the overseer invoked once per pass — so it capped model steps at 30 per pass, and a compaction reload already reset it. Upstream cloudflare#493 removed that per-pass cap as part of moving the compaction loop inside runAgent; this sync adopts that refactor as-is (the fork's resolution only threads its turn options through the new signature).

Whether passes need a bound back is an upstream design question — their trajectory evals (cloudflare#510cloudflare#512) exercise long turns, so re-adding a cap here would be a new fork behavioral divergence that could skew those. Not taking that on in the sync; worth raising upstream.

Comment on lines +1949 to +1950
Return to the last accepted version. Any gadgets or worktrees created by these
changes will be permanently deleted. Pending changes can&apos;t be restored.

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 Correct the discard warning for retained worktrees

Remove the claim that created worktrees are permanently deleted. If a chat creates and then modifies a worktree, this discard-all confirmation is shown, but the backend deliberately keeps worktrees across reverts and only rolls back their content and head (proposedChangeWorkpieceIds() documents that no revert deletes one). Users can therefore confirm this destructive-looking action expecting the checkout to disappear, yet it remains in the conversation.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

This string is upstream cloudflare#513's, adopted unchanged — the fork's ChatInterface changes in this sync are only the composer effort/prompt seam. Whether "created by these changes" accurately covers creation-discard versus modification-revert is an upstream UX-copy question, so leaving it as-is rather than creating a standing fork diff on a line upstream owns.

check.commit();
}
return catalog;
return buildContextCatalog(collections, await this.#loadSkills(collections));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep private Context metadata out of automatic catalogs

When an owner has an enabled private Context collection and shares a workspace, this now puts that collection's title, description, and skill metadata into every chat prompt without the former observer authorization or exclusion check. A collaborator who does not own that collection can therefore induce the agent to reveal its catalog metadata even though session reads would reject them; either omit private entries from the automatic catalog or restore a recipient-aware authorization boundary.

AGENTS.md reference: AGENTS.md:L33-L33

Useful? React with 👍 / 👎.

@bashandbone Adam Poulemanos (bashandbone) Sep 22, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Mechanism confirmed: loadEnabledContextCollections returns the account's owned private titles/descriptions, and cloudflare#267 dropped the authorizer, so that metadata now reaches every chat prompt unauthenticated. Two scoping notes: (1) this file is untouched by the fork — the sync adopts upstream cloudflare#267's contract verbatim, including its doc comment stating catalogs "may not need observer verification"; (2) the exposure is catalog metadata only — document reads still go through the session with per-collection observer verification (addObserver), and skill invocation still authorizes.

A fork fix would need recipient-aware catalogs, i.e. threading back the authorizer cloudflare#267 removed — a significant divergence from the new architecture, not something to take on inside a sync. Treating this as adopted-upstream-behavior; if we want a fork-side boundary here it deserves its own proposal.

Comment on lines +357 to +359
this.ctx.exports.UserDirectoryDurableObject.getByName("")
.syncUser({ id: profile.id, name: profile.name }, rev)
.then(() => {

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 Keep directory sync alive after the RPC returns

Register this background synchronization with this.ctx.waitUntil(). Both authentication and setOwnDisplayName() return without awaiting this promise, so the Durable Object event can finish while the cross-DO syncUser() call or its acknowledgement is still pending; the runtime may then cancel that work. In particular, a rename on a long-lived session can remain absent from the directory indefinitely because no later authentication occurs to retry it.

Useful? React with 👍 / 👎.

@bashandbone Adam Poulemanos (bashandbone) Sep 22, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

The failure mode is real but already designed for: this is upstream cloudflare#474's code, adopted unchanged, and its comment documents the choice — directoryRev advances only on success, so a cancelled sync "leaves it behind so the next authentication retries". A rename therefore goes stale only until that user's next authentication, not indefinitely. Upgrading fire-and-forget to waitUntil would be a (small) fork divergence from upstream's deliberate best-effort design; leaving as-is.

@bashandbone
Adam Poulemanos (bashandbone) merged commit 49d388d into main Sep 22, 2026
9 checks passed
@bashandbone
Adam Poulemanos (bashandbone) deleted the sync/foundation-2026-09-22 branch September 22, 2026 17:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants