Skip to content

🤖 fix: drop remote plugin snapshots when project trust changes outside the app - #7640

Merged
ThomasK33 merged 2 commits into
mainfrom
fix/7621-snapshot-trust-clear
Oct 26, 2026
Merged

ThomasK33 merged 2 commits into
mainfrom
fix/7621-snapshot-trust-clear

Conversation

@ThomasK33

@ThomasK33 ThomasK33 commented Oct 26, 2026 •

Copy link
Copy Markdown
Member

Summary

A project trust change made outside the app, by xum trust or any other writer of config.json, now drops the cached remote project plugin snapshots, as ProjectService.setTrust already does. Before this change, a revoke followed by a re-grant through the CLI could keep serving a snapshot from before the revoke until it was evicted or the backend restarted.

Background

Follow-up from the final check of #7592. Only setTrust called clearRemoteProjectPluginSnapshots. A trust change from another writer reaches the running backend only as a config.json change. A revoke always failed closed, because each turn computes trust per call and then gets no plugin containers. This PR fixes freshness, not security.

Implementation

  • ProjectService.watchProjectTrust() subscribes to Config.onConfigChanged. On each change it builds the sorted set of effectively trusted project paths (isProjectTrustedIn, so a project inherits its parentProjectPath owner's trust). When that set differs from the last one, it calls clearRemoteProjectPluginSnapshots(). A flip, a trusted project added and a trusted project removed all count. Edits that leave trust alone clear nothing.
  • Scope of the guarantee: the clear runs when this backend observes a change in the trusted set. Suppose Config's file watch merges a revoke and a re-grant into one notification. Then the trusted set looks unchanged, and the snapshot of that same checkout stays. A read that ran between the two writes did see the revoke. It failed closed, because turns compute trust on every call. The only cost is freshness: the coalesced re-grant does not refresh the snapshot. Clearing on every config change would drop all remote snapshots on each config save, which is frequent.
  • The handler reads only config.loadConfigOrDefault(). That is the config snapshot every config-change listener shares: one stat, plus one parse when the file changed. It runs no other I/O.
  • Lifetime: ServiceContainer starts the watch as a new best-effort core startup step, projectService.watchProjectTrust. ProjectService.beginShutdown() unsubscribes it, and ServiceContainer.dispose() already calls that. The method never throws: it catches and logs both at start and in the handler. An own save's notification can pass a listener's throw to the saving caller, so the handler must not throw.
  • The subscription keeps Config's config.json directory watch open for the whole backend life. Config opens that watch only while it has a listener. In a full backend, other services already subscribe (ContextManagementService, MemoryService, AgentResourceLimitsService), so the watch is normally open already.
  • remoteProjectPlugins.ts is unchanged. The hook sits in projectService.ts, next to the existing setTrust clear.

Validation

  • projectService.test.ts: a trusted remote checkout's snapshot is read once. A second Config instance on the same root then writes config.json, as the CLI does.
    • A display-name-only edit keeps the snapshot.
    • A revoke followed by a re-grant makes the next read run a new listing.
    • Without the clear call, the test fails (Expected: 2, Received: 1).
  • serviceContainer.test.ts: the list of core step names includes the new step.

Out of scope

The same out-of-app trust change also skips mcpServerManager.applyProjectTrust: #7639.

Fixes #7621


Generated with xum • Model: anthropic:claude-opus-5-5 • Thinking: high • Cost: $295.90

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 26, 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-10-26T19:45:48.219100Z 5630691 Manual request
🔒 Security Review ✅ Completed 2026-10-26T19:46:24.902856Z 5630691 Manual request
ℹ️ 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" or "@codex security review".

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

@ThomasK33

Copy link
Copy Markdown
Member Author

Perf clearance (perf owner, workspace 8e18f4aada): CLEARED at head 563069192a (verified with ls-remote; product files projectService.ts 41/1 and serviceContainer.ts 7/0).

What I checked:

  1. Startup step projectService.watchProjectTrust: runs once. loadConfigOrDefault() returns the already-loaded snapshot (one stat), then an O(projects) filter. No new I/O.
  2. Each config change: own saves do not store a snapshot (storeLoadedSnapshot runs only on a read), so the first read after a save parses config.json. That parse already happens today: AgentResourceLimitsService reads loadConfigOrDefault() after every change (agentResourceLimitsService.ts:39-40, read at :100). This listener does the same read a microtask earlier, and the later read then reuses the snapshot. Net: no extra parse per save in a full backend.
  3. Lifetime: the config.json watch is already open for the backend's life through the existing listeners. Unsubscribing in beginShutdown is fine.

Conditions:

  • Keep the listener O(projects) with no I/O besides the shared snapshot.
  • A new push that changes product files needs a new clearance.
  • If a later change removes the other per-change config readers, this listener becomes a new per-save parse (about 200 ms on a 38 MB config): that change must ask for a perf check.

Generated with xum • Model: anthropic:claude-opus-5-5 • Thinking: high

@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: 563069192a

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

Comment thread src/node/services/projectService.ts
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@github-actions

Copy link
Copy Markdown
Contributor

🟢 e2e stories (Haiku): 14 passed

22 agent steps · 7 replayed from cache · 15 model calls · 46.3k tokens (28% cached)
Spend: $0.08 of the $1.00 cap (proxy ledger)

All 14 tests in 9 files
Test Agent Time
🟢 chatRestart.e2e.ts · 1 passed 1 step · 1 call 6.4s
🟢 a chat survives a restart of the server 1 step · 1 call 6.4s
🟢 compaction.e2e.ts · 1 passed 1 step · 1 call 4.9s
🟢 /compact replaces the old chat with a summary 1 step · 1 call 4.9s
🟢 pluginTrustRestart.e2e.ts · 1 passed 4 steps · 2 calls 10.4s
🟢 a trusted plugin hook stays trusted after the server restarts 4 steps · 2 calls 10.4s
🟢 settingsRoundTrip.e2e.ts · 1 passed 2 steps · 1 call 9.4s
🟢 a chat survives trips through Settings 2 steps · 1 call 9.4s
🟢 sidebarPerWorkspace.e2e.ts · 1 passed 4 steps · 1 call 4.2s
🟢 a new workspace starts with its right sidebar collapsed while another keeps it open 4 steps · 1 call 4.2s
🟢 smoke.e2e.ts · 1 passed 2 steps · 1 call 2.9s
🟢 the agent opens the playground workspace 2 steps · 1 call 2.9s
🟢 streamErrors.e2e.ts · 1 passed 1 step · 1 call 6.8s
🟢 stream errors show in the chat, and the chat recovers 1 step · 1 call 6.8s
🟢 todoNudge.e2e.ts · 1 passed 1 step · 1 call 2.8s
🟢 the agent finishes open TODO items after the turn-end nudge 1 step · 1 call 2.8s
🟢 touchAlignment.e2e.ts · 6 passed 6 steps · 6 calls 25.6s
🟢 the Settings close button is level with the title on a touch screen 390px wide 1 step · 1 call 3.8s
🟢 the Settings close button is level with the title on a touch screen 768px wide 1 step · 1 call 8.9s
🟢 Backup settings checkboxes are small and level with their labels on a touch phone 1 step · 1 call 4.1s
🟢 the heartbeat checkbox is small and level with its label on a touch phone 1 step · 1 call 3.2s
🟢 a reply's model and time stay on one line on a touch phone 1 step · 1 call 3.2s
🟢 the workspace actions menu stays on the title row on a touch phone 1 step · 1 call 2.4s

e2e 0.20.0 · 1m 16s · web · run artifacts

🟢 e2e Electron stories (Haiku): 3 passed

3 agent steps · 3 model calls · 8.0k tokens (32% cached)
Spend: $0.01 of the $1.00 cap (proxy ledger)

All 3 tests in 3 files
Test Agent Time
🟢 electron/deepLink.e2e.ts · 1 passed 1 step · 1 call 6.5s
🟢 a deep link of a second launch starts a chat in the running app 1 step · 1 call 6.5s
🟢 electron/menuBar.e2e.ts · 1 passed 1 step · 1 call 5.3s
🟢 the workspace menu bar is the desktop title bar 1 step · 1 call 5.3s
🟢 electron/smoke.e2e.ts · 1 passed 1 step · 1 call 8.9s
🟢 Xum Electron opens the playground workspace 1 step · 1 call 8.9s

e2e 0.20.0 · 23.0s · electron · run artifacts

🟢 e2e agent repros (Haiku): no tests selected

Spend: $0.00 of the $1.00 cap (proxy ledger)

e2e 0.20.0 · 5.3s · 2 targets (web, phone) · run artifacts

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Chef's kiss.

Reviewed commit: 563069192a

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

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review · Automatically triggered

Security review completed. No security issues were found in this pull request.

Reviewed commit: 563069192a

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

@ThomasK33

Copy link
Copy Markdown
Member Author

Readiness record for 563069192a673669f2c40e4a194a91da1d8cd2f7:

  1. Codex review: round 1 found one P2 (P2 = Codex's medium severity): a revoke and re-grant merged into one notification skip the clear. I rejected it with a reason on the thread. That case costs only freshness, a revoke the backend reads still fails closed, and clearing on every config save would force a cold remote listing after each save. Round 2 at this head found nothing (thumbs-up). All threads are resolved.
  2. Perf owner: cleared this head, with conditions (issuecomment-6425629583).
  3. CI: Required passes. Its first run failed only because the "Codex Comments" check had run while the thread was still open, so I reran those two jobs.
  4. Local: make static-check, make lint-eslint, make crap-check, projectService.test.ts and serviceContainer.test.ts pass. Without the clear call, the new test fails (Expected 2, Received 1).
  5. Independent final check (clean context): READY. It found one false premise in my thread reply and in the PR body, and I corrected both. It also asked whether xum run and xum workflow (createCoreServices) need the watch. I decided they do not. Each runs one task and exits, and a trust change from outside the app during that time is the same freshness-only case, because trust is computed on every call. Its third note is already a condition of the perf clearance.
  6. Review count: 3 of 6. I stopped there because the gates are green and no findings are open.

Generated with xum • Model: anthropic:claude-opus-5-5 • Thinking: high

@ThomasK33
ThomasK33 added this pull request to the merge queue Oct 26, 2026
Merged via the queue into main with commit 97d86d7 Oct 26, 2026
63 of 66 checks passed
@ThomasK33
ThomasK33 deleted the fix/7621-snapshot-trust-clear branch October 26, 2026 20:05
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.

Remote project plugin snapshots are not cleared when trust changes outside the app

1 participant