Skip to content

fix(claude): keep the bot's claude.ai account out of CI sessions - #1328

Merged
max-sixty merged 2 commits into
mainfrom
fix/opt-out-claude-ai-sync
Sep 20, 2026
Merged

max-sixty merged 2 commits into
mainfrom
fix/opt-out-claude-ai-sync

Conversation

@tend-agent

@tend-agent tend-agent commented Sep 20, 2026 •

Copy link
Copy Markdown
Collaborator

Claude Code syncs the skills and plugins enabled on the signed-in claude.ai account into terminal sessions, re-syncing every 10 minutes while it is on, and separately auto-fetches and mounts that account's MCP cloud connectors — headless sessions included. Tend's sessions sign in with the consumer's bot account, so that account becomes an instruction and tool source for every CI session — in a process that pushes commits, opens PRs, and posts as the bot. Nothing in a consumer's repo reviews it, and nothing in tend's own review path sees it either: the whole premise of Fixed prompts and marketplace skills in docs/security-model.md is that the instructions and the tools come from the composite action and the tend marketplace rather than from anywhere an attacker or an accident can reach.

This writes syncClaudeAiSkills: false, syncClaudeAiPlugins: false and disableClaudeAiConnectors: true into the .claude/settings.local.json the action already composes.

Details from the settings schema in the shipped CLI decide the placement, and are why this is worth writing now rather than when it becomes visible:

  • The opt-outs are honored from .claude/settings.local.json and --settings, for that workspace or invocation — exactly the layer tend writes, so no new file or user-settings layer is needed. disableClaudeAiConnectors is honored true from any source, so the same file carries all three.
  • The sync keys are explicitly not read from project settings.json — so the nearby key would have been the wrong one to reach for.
  • For the sync pair, only false is honored, because the feature turns on server-side per account. There is no event to react to and no version that introduces it: the flip happens outside the repo, at which point sessions already have the synced set. Writing the refusal ahead of it is the only ordering available.
Where the schema says this

From the syncClaudeAiSkills description in the pinned claude build (2.1.270), which already carries the key:

Set to false to turn off syncing of the skills you have enabled on claude.ai. In your user settings (or managed settings): nothing more is downloaded, previously synced skills (~/.claude/skills/synced) can no longer be run, are hidden from every session started afterwards, and are moved to ~/.claude/skills/.trash at the next launch […]. In .claude/settings.local.json or --settings: downloads stop and synced skills are blocked and hidden for sessions in that workspace or invocation only (nothing is moved). Not read from project settings (.claude/settings.json). Only false is honored — the feature is enabled server-side for your account, so setting true does not turn it on early. While it is on, synced skills are available in every session, re-synced every 10 minutes […]

syncClaudeAiPlugins carries the same wording for ~/.claude/plugins/synced. The user-facing announcement is in the 2.1.275 release notes ("Added syncing of the skills and plugins enabled on your claude.ai account to terminal sessions signed in with it"), with 2.1.273 adding the plugin scope to the sign-in request.

And disableClaudeAiConnectors in the same build:

When true in any settings source, claude.ai MCP cloud connectors are not auto-fetched or connected. Only gates auto-fetched connectors — a claudeai-proxy server passed explicitly (e.g. via --mcp-config or the SDK mcpServers option) still follows the normal MCP config trust flow. Any-source-true wins: a project can opt out, but a project-level false cannot override a user-level true.

Nothing here passes a connector explicitly, so the auto-fetch is the whole surface. That fetch is not interactive-only: the build carries a headless mount path (headlessConnectorMountInFlight) over the clients whose config scope is claudeai.

test_launch_writes_settings_inside_the_existing_sandbox already asserts the settings file by exact equality, so it is the coverage — it fails on a key that stops being written. Found by the weekly harness release-note review; the connector key came out of the review of this PR, since it is the same account surface and the same three lines.

…ions

Claude Code syncs the skills and plugins enabled on the signed-in
claude.ai account into every session, re-syncing while it is on. Tend
signs in with the consumer's bot account, so that account would become
an unreviewed instruction and tool source for a process that pushes and
posts as the bot.

Both keys are honored from .claude/settings.local.json and --settings,
which is the layer the action already writes, and only false is honored
because the feature turns on server-side per account.
max-sixty pushed a commit that referenced this pull request Sep 20, 2026
Moves `claude_version` from 2.1.270 to 2.1.278, npm's current `latest`
dist-tag. Eight stable releases, of which four carry changes to surfaces
tend depends on.

**Headless execution.** 2.1.277 fixes `claude -p` and Agent SDK sessions
that "could hang with no result after an internal error; they now report
the error and exit with code 1". That is tend's exact completion
contract — `run_claude.py` decides the verdict from the process exit
plus the last `result` event, and treats a turn with no result event as
never completed. Before this fix such a run had no exit at all and
burned the step's timeout to `SIGTERM`; now it exits 1 and the
annotation names the error. 2.1.277 also fixes conversations failing
every request with "text content blocks must be non-empty" when an
earlier assistant turn held an empty text block.

**`$TMPDIR` in Bash.** 2.1.277 fixes `$TMPDIR` expanding empty in Bash
commands that run outside the sandbox while sandboxing is enabled.
Tend's boundary is SRT rather than Claude Code's own sandboxing, so this
is not a path tend takes today — noting it because `$TMPDIR` is
load-bearing in the shipped skills, which tell every session to write
scratch files there.

**Plugin loading.** 2.1.277 fixes `claude plugin install` "sometimes
failing and breaking the installed copy when reinstalling a plugin
version that a session or another program was using", and fixes plugins
from the official marketplace being recorded without their commit in
`installed_plugins.json`. `install-tend-plugins.sh` runs two `claude
plugin install` calls per job, so a fix to that path is directly in
tend's boot sequence.

**Model selection.** 2.1.274 fixes subagents with `model: "opus"`
leaving the session's model on Bedrock/Vertex/Foundry when the id has no
recognizable family — not a path tend takes, since consumers
authenticate against the first-party API. Flagged only because
model-alias resolution is the drift a stale pin causes.

No change to the stream-json event shapes the verdict parses, to
`--permission-mode`, or to `--append-system-prompt`/`--settings`
handling across these eight releases.

**claude.ai account sync — this PR should not merge ahead of #1328.**
2.1.275 added "syncing of the skills and plugins enabled on your
claude.ai account to terminal sessions signed in with it", opt out with
`syncClaudeAiSkills: false` or `syncClaudeAiPlugins: false`. Earlier
notes confine that sync to cloud sessions and Claude Desktop (2.1.269
names "skills synced from claude.ai in cloud sessions", 2.1.261 "cloud
sessions discarding a plugin synced from claude.ai"), so the
terminal-session capability arrives with this release rather than
server-side: `claude -p` under this action is a terminal session signed
in with the bot's OAuth token, and at 2.1.270 the client has no such
sync. #1328 writes the refusal into the session's `--settings`; merged
before it, this pin loads whatever is enabled on the bot's account into
every consumer's CI session, in a process that pushes commits and posts
as the bot. Whether that fetch clears the sandbox's `--allow-hosts`
(`api.anthropic.com` and the GitHub hosts; not `claude.ai`) is
unverified, and decides how urgent the ordering is rather than whether
it holds.

<details><summary>Releases crossed</summary>


[2.1.271](https://github.com/anthropics/claude-code/releases/tag/v2.1.271),
[2.1.272](https://github.com/anthropics/claude-code/releases/tag/v2.1.272),
[2.1.273](https://github.com/anthropics/claude-code/releases/tag/v2.1.273),
[2.1.274](https://github.com/anthropics/claude-code/releases/tag/v2.1.274),
[2.1.275](https://github.com/anthropics/claude-code/releases/tag/v2.1.275),
[2.1.276](https://github.com/anthropics/claude-code/releases/tag/v2.1.276),
[2.1.277](https://github.com/anthropics/claude-code/releases/tag/v2.1.277),
[2.1.278](https://github.com/anthropics/claude-code/releases/tag/v2.1.278).

2.1.276 is a single-line regression fix for 2.1.275 (`400 … Input tag
'advisor_20260301'` behind a proxy or gateway) — worth naming because
tend runs every session through mitmproxy, so 2.1.275 on its own would
have been the wrong stopping point.

</details>

Weekly pin sweep. The sandbox boundary's own pins are held this week for
a separate reason — see #1329.

Co-authored-by: tend-agent <tend-agent@users.noreply.github.com>

@tend-agent tend-agent left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

syncClaudeAiSkills/syncClaudeAiPlugins close two of the three doors the bot's claude.ai account has into a session. The third is disableClaudeAiConnectors, which this settings dict doesn't set: claude.ai MCP cloud connectors are auto-fetched and connected unless that key is true in some settings source, and the mount runs in headless sessions too — the same pinned 2.1.270 build carries a headlessConnectorMountInFlight path over the clients whose config scope is claudeai. So a connector enabled on the bot account still arrives as tools in every consumer's CI session: the same unreviewed surface this PR closes for skills and plugins, and the same thing Fixed prompts and marketplace skills in docs/security-model.md claims about the tools the agent has access to, not just its instructions.

The layer this PR already writes is enough to refuse it. From that key's description in the same build:

When true in any settings source, claude.ai MCP cloud connectors are not auto-fetched or connected. Only gates auto-fetched connectors — a claudeai-proxy server passed explicitly (e.g. via --mcp-config or the SDK mcpServers option) still follows the normal MCP config trust flow. Any-source-true wins: a project can opt out, but a project-level false cannot override a user-level true.

I'm pushing "disableClaudeAiConnectors": True into the same dict and the same exact-equality assertion, and retitling the PR around the account surface rather than the two sync keys — it is one policy, not two, and the alternative is a second PR editing the same three lines.

claude.ai connectors are auto-fetched and mounted in headless sessions
unless disableClaudeAiConnectors is true in some settings source, so the
bot account could still add tools to every consumer's CI session after
the two sync keys closed skills and plugins.
@tend-agent tend-agent changed the title fix(claude): keep claude.ai account skills and plugins out of CI sessions fix(claude): keep the bot's claude.ai account out of CI sessions Sep 20, 2026
@max-sixty
max-sixty merged commit b953f68 into main Sep 20, 2026
11 checks passed
@max-sixty
max-sixty deleted the fix/opt-out-claude-ai-sync branch September 20, 2026 17:57
@max-sixty max-sixty mentioned this pull request Sep 21, 2026
max-sixty added a commit that referenced this pull request Sep 21, 2026
Version bump, lockfile, and the `## 0.2.12` section the release workflow
publishes verbatim as the GitHub Release notes.

17 commits since 0.2.11. The headline is the sandbox layout: the agent
runs in the job's own checkout and home through a copy-on-write view, so
what a consumer's `setup:` steps prepare reaches the agent without being
rebuilt in `sandbox_setup:`, and the job environment crosses into the
sandbox in a `0600` file rather than on `sudo`'s command line
([#1333](#1333),
[#1334](#1334)). Alongside it:
`/tmp` is writable inside the sandbox as scratch private to the run
([#1324](#1324)), CI sessions
ignore the bot's claude.ai skills, plugins and connectors
([#1328](#1328)), the SRT launch
retries a transient bridge-socket failure
([#1327](#1327)), and a cancelled
gating check polls as unverified rather than green
([#1323](#1323)).

Each bullet was written from the commit's diff rather than its subject.
No real agent session has run through the new view yet (CI's
`test-sandbox` lifecycle runs stub `claude`/`codex` binaries), so the
release's live check after tagging is the first one that does.

> _This was written by Claude Code on behalf of max-sixty_

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
max-sixty pushed a commit to max-sixty/leaf that referenced this pull request Sep 21, 2026
Regenerates the nine `tend-*` workflows against tend 0.2.13 (from
0.2.11). The committed diff is only the pinned action ref and the `uv`
setup version (`0.12.13` → `0.12.17`); everything below rides in the
action and the plugin it loads.

## What changes for this repo

**A cancelled gating check no longer reads as green.**
`poll_pr_checks.py` now treats only `SUCCESS`, `NEUTRAL`, and `SKIPPED`
as green and exits 2 — unverified — on anything else
([max-sixty/tend#1323](max-sixty/tend#1323)).
Cancelled runs are routine here: main serializes the complete suite, so
a newer commit replaces the one waiting for its turn, and `running-tend`
already carries a rule for reading that shape. Under 0.2.11 a session
polling its own push could report such a check as verified; it now has
to say it could not verify.

**CI sessions ignore the bot's claude.ai account.** Skills, plugins, and
MCP connectors enabled on the `leaf-agent` account no longer load into
sessions that push and post as it
([max-sixty/tend#1328](max-sixty/tend#1328)).
Sessions run with this repo's skills and tend's own, and nothing else.

**`/tmp` is writable inside the sandbox**, as scratch private to the run
([max-sixty/tend#1324](max-sixty/tend#1324)). A
tool that hard-codes a `/tmp` path no longer needs an override;
`$TMPDIR` stays the documented place to write.

**The sandbox launch retries a transient bridge-socket failure**, up to
three attempts
([max-sixty/tend#1327](max-sixty/tend#1327)).
SRT gives socat 600 ms to bind, and a slow runner previously lost the
whole job before the agent started.

Three plugin-side fixes also land: `review-runs` can now cancel a run
parked in GitHub's `waiting` state that holds its concurrency group
without ever concluding
([#1320](max-sixty/tend#1320)); `ground-claims`
reads a past run's code at that run's commit rather than at the current
default branch ([#1319](max-sixty/tend#1319));
and `run-tend` corrects its list of instruction paths restored to the
base version on a PR
([#1311](max-sixty/tend#1311)).

## No configuration change needed

0.2.12 moved the agent into the job's own checkout through a
copy-on-write view, which would have changed where dependency setup
belongs. 0.2.13 reverts it
([#1339](max-sixty/tend#1339)): `setup:` and
`sandbox_setup:` behave as they did at 0.2.11, so `.config/tend.yaml` is
unchanged here. This repo pins no `model:`, so the harness default
applies as before.

Full comparison:
[0.2.11...0.2.13](max-sixty/tend@0.2.11...0.2.13).

Co-authored-by: leaf-agent <318509791+leaf-agent@users.noreply.github.com>
max-sixty pushed a commit that referenced this pull request Sep 21, 2026
…ttings that refuse it (#1349)

`docs/security-model.md`'s **Fixed prompts and marketplace skills**
claims an attacker can influence what the agent reads but not "the
*instructions* it follows or the *tools* it has access to". Until
[#1328](#1328) that claim had a
hole the doc never named: Claude Code syncs the skills and plugins
enabled on the signed-in claude.ai account into a session and separately
auto-fetches that account's MCP connectors, so the bot account was a
second instruction and tool source for every consumer's CI session.
#1328 closed it in `shared/steps/run_claude.py` and cited this very
section as its premise, but left the section itself unchanged — so the
doc still asserts the property without recording the mechanism that
makes it hold.

This adds a paragraph under that heading naming the surface, the three
settings keys that refuse it, and the two constraints on how they work:
`disableClaudeAiConnectors` is honored true from any source, while the
sync pair is honored only as `false` and only from the session's own
settings, which is why the refusal is written ahead of the feature
rather than in response to it. Both are in the `settings()` docstring in
`shared/steps/run_claude.py`; the doc is where a reader auditing the
boundary would look for them.

The claims restate what that docstring and the 0.2.12 changelog entry
already commit to — this PR moves them into the threat model rather than
deriving anything new about Claude Code's behavior.

Documentation only, so there is no regression test. The control it
describes is already pinned: the launch test asserts the settings file
exactly, so a key that stops being written fails the suite.

---------

Co-authored-by: tend-agent <270458913+tend-agent@users.noreply.github.com>
max-sixty pushed a commit to max-sixty/worktrunk that referenced this pull request Sep 21, 2026
Regenerates the `tend-*` workflows with tend 0.2.13 (from 0.2.11).
Mechanically the diff is the action pin `max-sixty/tend/claude@0.2.11` →
`@0.2.13` in the eight workflows that run the agent, the
`astral-sh/setup-uv` pin `0.12.13` → `0.12.17` in the two that install
uv directly (`tend-mention`, `tend-notifications`), and a regenerated
header comment in all nine.

The upgrade that matters for this repo is in CI polling.
`poll_pr_checks.py` previously treated any gating check that settled
without a red conclusion as green — so a required check belonging to a
**cancelled** or **stale** run, or a `COMPLETED` check carrying no
conclusion at all, let the bot report a PR as green when nothing had
actually verified it. 0.2.13 splits those out as `unverified` and gives
`poll` a distinct exit code for them (max-sixty/tend#1323). Practically:
fewer "checks went green" claims on PRs whose required jobs were
cancelled, at the cost of some polls ending unverified rather than
green.

The rest, in rough order of how visible it is here:

- **Sandbox reliability** — the sandbox runtime now retries past a
transient bridge-socket failure at launch (max-sixty/tend#1327). That
failure is one of the shapes that produces a `tend-outage` issue, so it
should show up as fewer of those.
- **Sandbox environment** — root `/tmp` is writable inside the sandbox
(max-sixty/tend#1324); the `run-tend` skill's note that it was read-only
is gone. This repo's `running-tend` skill still restates that note, so
it goes stale when this merges — I'll follow up separately rather than
mix a skill edit into the generated-workflow diff.
- **Instruction-path pinning** — the `run-tend` skill's description of
which paths get restored from the PR base, and on which events, was
wrong in 0.2.11; `.agents/` is now named alongside `.claude/`, and the
event list is corrected (max-sixty/tend#1311). The restore behavior
itself is unchanged.
- **Auth hygiene** — the bot's claude.ai account is kept out of CI
sessions (max-sixty/tend#1328).
- **Harness pins** — claude-code 2.1.278 (max-sixty/tend#1330), uv
0.12.17 (max-sixty/tend#1331).

`.config/tend.yaml` pins no model, so the harness-default check found
nothing to adjust.

Full comparison:
max-sixty/tend@0.2.11...0.2.13

<details><summary>Copy-on-write sandbox view: landed and reverted within
this range</summary>

max-sixty/tend#1333 and max-sixty/tend#1334 moved the agent into the
job's own checkout and home through a copy-on-write view;
max-sixty/tend#1339 reverted both before 0.2.13. Nothing from that work
is in this diff — noted so a later reader doesn't chase it out of the
commit list.

</details>

Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
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