Repository navigation
fix(claude): keep the bot's claude.ai account out of CI sessions - #1328
Conversation
…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.
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
left a comment
There was a problem hiding this comment.
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-configor the SDKmcpServersoption) 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.
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>
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>
…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>
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>
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.mdis 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: falseanddisableClaudeAiConnectors: trueinto the.claude/settings.local.jsonthe 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:
.claude/settings.local.jsonand--settings, for that workspace or invocation — exactly the layer tend writes, so no new file or user-settings layer is needed.disableClaudeAiConnectorsis honored true from any source, so the same file carries all three.settings.json— so the nearby key would have been the wrong one to reach for.falseis 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
syncClaudeAiSkillsdescription in the pinnedclaudebuild (2.1.270), which already carries the key:syncClaudeAiPluginscarries 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
disableClaudeAiConnectorsin the same build: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 configscopeisclaudeai.test_launch_writes_settings_inside_the_existing_sandboxalready 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.