Skip to content

[design] Account-level connections are rendered inside one agent's Settings — disconnecting Gmail there disconnects it everywhere #355

Closed

Description

@serge-ivo

Four scopes, one tab

SettingsTab.tsx is 1,154 lines and mixes settings that belong to four different things. The tab is
titled with one agent's name, so everything on it reads as this agent — including the parts that
silently change every agent.

What Actual scope Where it is written
Instance name, agent settings, behaviour, runner node, tool toggles, write consent, supervision, connections, triggers, loop presets this instance agent_instances.config
Voice, Translation account default + explicit per-instance override /v1/preferences ← PrefOverride
Connect / disconnect Gmail, Google Drive, Zoho WorkDrive the whole account connector_tokens
Link GitHub username the whole account users.linked_github_login
Drive/WorkDrive folder grants, email permission checkbox this instance connector_grants, AgentState.permissions

Rows 3 and 4 are the leak. Disconnecting Gmail from the Coder's Settings tab disconnects it for the
Job Application Assistant too. Nothing on screen says so except one 12px line under the GitHub row
(:939) — and Gmail, Drive and WorkDrive don't even have that.

The disconnect confirmations make the same mistake from the other side:

"Disconnect Gmail? Agents will no longer be able to read your inbox for sign-in links."

Correct — plural, account-wide — but you are reading it inside one agent's settings, having clicked a
button in a card about that agent.

The pattern already exists in this file

Voice and Translation get this exactly right (#211): an account default, an explicit "Use my
defaults / Customise for this agent"
radio, and a "Use my defaults" that DELETEs the override.
Preferences.tsx's own header comment states the principle — "Only [what an agent IS] and [the
danger zone] are properties of an agent."

A connection is not a property of an agent. A grant is.

Proposal

Move the account half to Preferences. Add a Connections section there — connect/disconnect for
GitHub, Gmail, Drive, WorkDrive, and any registry connector needing OAuth. That page is already
defined as "what every agent falls back to", and it already carries timezone, voice and translation
on the same footing.

Leave the instance half on the instance — and only when it can do something:

Where an instance needs a connection the account doesn't have, link to Preferences rather than
re-rendering connect UI in two places — the same shape as the runner card's pags up remedy.

Why not just add "account-level" labels

Because it doesn't fix what the reader is doing. Someone on the Coder's Settings tab is configuring
the Coder; a label asking them to notice that one card is different is a worse guarantee than putting
the card where its scope is obvious. It is also what #211 already decided for voice — this is the
same argument for the connection rows it didn't reach.

After this

SettingsTab.tsx is on #305's list. Splitting it before this and #352 just relocates the clutter;
after them it separates cleanly into Identity · Runtime · Permissions · Teamwork · Automation ·
Overrides · Danger.

Related: #211 (the override pattern), #305 (large-file split), #352 (registry), #353 (unconfigured
connectors).

Activity

  1. serge-ivo commented on Aug 7, 2026

    @serge-ivo
    ContributorAuthor

    This is the primary fix for the settings clutter, not a follow-on

    Filed as a scope-hygiene issue and cross-referenced to #352 as though #352 did the real work. Checking
    #352 afterwards showed the opposite.

    Drive and WorkDrive expose no agent-callable tools, and Gmail's is deliberately non-declarable
    (see the correction on #352). So capabilities.tools cannot gate any of the three, and #352 cannot
    remove them from the instance page without first inventing tools that do not exist.

    Moving connect/disconnect to the account page removes them today, with no registry work. Once the
    instance shows only a grant — and a grant is meaningless without an account connection — the block
    renders on the condition status.connected. On this deployment right now:

    That is the whole reported symptom, fixed by putting each control where its scope already is.

    The residual, stated honestly

    Connect Drive once at account level and the grant box comes back on all 26 instances, including the
    four terminal Operators that will never read a document. Gating that needs a capability axis, which
    is what #352 is for. So the two are complementary — this one fixes the symptom and the scope lie,
    #352 fixes the steady state — but this one does not depend on it and should not wait for it.

    Not a pure move

    Preferences.tsx has no connector UI today, so the Connections section is new work there, not a
    transplant. The existing rows in SettingsTab.tsx:931-1075 carry connect/disconnect handlers,
    confirm dialogs and status polling that mostly lift as-is; what has to be written is the section
    shell and the empty states.

    Suggested sequence: #353 → #354 → this → #352.

  2. serge-ivo commented on Aug 7, 2026

    @serge-ivo
    ContributorAuthor

    Assessment: do it — this is the right diagnosis of a permission-comprehension bug. Three cautions, one of which is that the destination page has the same problem one level up.

    Agreeing with the body and the correction on the main point, so this comment is only the parts I'd change.

    First, the scope table is wrong about two storage locations

    Worth fixing before anyone implements from it, because both names read as purpose-built tables and neither exists:

    Row Table named Actual
    Connect/disconnect Gmail, Drive, WorkDrive connector_tokens user_api_keys, keyed (user_id, provider) where provider is the connector id — the same table and the same helper (saveConnectorRefreshToken, lib/connector-oauth.ts:120) that every registry OAuth connector uses
    Drive/WorkDrive folder grants connector_grants instance_connector_grants (lib/connector-grants.ts:111)

    The first correction matters beyond pedantry: it is why #352 Stage 1 is nearly free, and why the Connections section should be built as a loop over the registry rather than three more hardcoded blocks. See my comment there — short version: land the GET /v1/connectors list route first and this section is a .map(), otherwise it is a third hand-written copy that #352 then deletes.

    Tested: the destination page already breaks its own promise

    Preferences.tsx subtitle:

    How you speak, hear and read. These apply to every agent — any one of them can be customised on its own Settings tab.

    That is true of Voice and Translation. It is false of the two other things already on the page:

    • Timezone (c039562) has no per-instance override at all. Its own save confirmation says so — "Saved — every agent uses this", versus Voice's "applies to every agent". Two different promises, one paragraph apart.
    • Appearance / text size is localStorage, per-browser, and has nothing to do with agents.

    So the page is not, as the body has it, "already defined as what every agent falls back to". It is already "the account-scoped page", and its header is the stale part. That matters here because a connection is the least overridable thing yet: there is exactly one Gmail account, and no per-instance variant of it can exist. Dropping connect/disconnect under a header that promises "any one of them can be customised on its own Settings tab" reproduces the exact misread this ticket exists to fix, one level up and with the destructive buttons on it.

    So: move the cards and rewrite the subtitle in the same change. Something in the shape of "Your account — connections, timezone and how you speak, hear and read. Voice and translation can be overridden per agent; everything else applies everywhere." One sentence, and it is the whole comprehension fix, because after this the page is the definition of account scope rather than a page that happens to hold some.

    The move makes an existing defect worse unless it carries one more thing

    Filed separately as #357: disconnecting Drive/WorkDrive deletes only the token row (routes/drive.ts:91). Every instance's folder grants survive in instance_connector_grants, nothing prunes them, and reconnecting silently restores every one of them — no user action, nothing displayed. The grant is the entire access-control story for that connector.

    Today the disconnect button at least sits next to one of the instances it affects. After this change it sits on a page that has no idea any grants exist, and reconnect is one click. So the Connections section should show the blast radius: "Google Drive — connected · 3 agents hold 5 folder grants", and the disconnect confirmation should name the number rather than saying "Agents will no longer be able to…". instance_connector_grants is queryable by (user_id, provider) and nothing queries it that way today; that is the one new read this section needs.

    That also fixes the confirmation-copy complaint in the body properly. The problem with "Agents will no longer be able to read your inbox" is not only that it is plural in a singular context — it is that it does not say which agents, when the system knows.

    What I would not build

    1. A "Use my defaults / Customise for this agent" control for connections. #211 is the right precedent for where these live and the wrong one for what shape they take. Voice has a real per-instance variant; a connection does not. Copying the override radio across would ship a control that promises per-agent scoping and cannot deliver it — the same confusion this ticket is closing, this time with an affordance backing it up. Connections get no override control, and the subtitle should say which sections have one.

    2. A scope-badge / "account-level" labelling component. The body already argues against labels as the fix, and I'd go further: don't build the generalised version afterwards either. Once the two genuinely account-scoped controls move, nothing left on the instance tab needs a badge — and a badge component is an invitation to leave the next account-scoped control on an instance page "because it's labelled". The rule the move establishes ("if it's account-scoped it isn't on an instance page") is worth more than any component, and a badge system quietly repeals it.

    3. Folding connections into the /v1/preferences payload. They share a page, not a resource. PUT /v1/preferences is a JSON blob of user preferences; a connection is an OAuth lifecycle with a redirect, a bind cookie, a token row and a disconnect. Keep them on their own routes and let the page compose them — otherwise the preferences endpoint grows a callback surface, and #352 Stage 2 (moving Drive onto the generic connector OAuth flow) has to unpick it.

    4. #305's SettingsTab split, in this change. Agreeing with the body and restating it because it is the tempting one: the file is 1,154 lines and this touches ~145 of them. Splitting first relocates the clutter into new files and then has to be redone.

    5. Moving the folder grant or the email permission checkbox. Both are correctly per-instance today. The grant box should render on status.connected (which is what does the work, per the correction above), and the email checkbox should probably render on connected too — right now agent-think.ts:638 offers find_confirmation_link on the permission flag alone, so after a Gmail disconnect an agent is still handed a tool it cannot use. That is a small honesty fix, not a scope change.

    Where GitHub goes is a real question the ticket skips

    users.linked_github_login is stored as identity, Preferences.tsx's own header comment reserves identity for Profile ("account, profile, candidate profile, API token, billing, keys"), and it is used as a connection (repo access, build status). It is the only one of the four with a scope note today (:939) and the only one that is not an OAuth token.

    I'd put it with the connections anyway — nobody hunting for "where do I connect GitHub" looks under identity — but say so explicitly in the ticket rather than letting the implementer discover the ambiguity. Note also that Profile.tsx is currently owned by another change, so a Connections-on-Preferences answer avoids a collision as well as being the better one.

    Sequence

    Unchanged from the correction except for one insertion: #353 → #354 → #352 Stage 1 (declare + GET /v1/connectors) → this → #352 Stage 2. Stage 1 is smaller than this ticket and turns its main piece of new work into a loop.

  3. serge-ivo commented on Aug 7, 2026

    @serge-ivo
    ContributorAuthor

    #357 is the data-side half of this issue and was filed independently while assessing it. Verified it: DELETE /v1/drive/google (routes/drive.ts:91) really does delete only the user_api_keys row, leaving every instance's instance_connector_grants intact.

    The two are the same confusion seen from two directions:

    That makes #357 an argument for this one rather than a separate concern: once connect/disconnect lives on the account page, "disconnecting this affects N instances' folder grants" is a sentence that has somewhere to go. Doing #357's pruning without this leaves the warning stranded in a per-agent card that should not be showing an account control at all.

  4. serge-ivo commented on Aug 7, 2026

    @serge-ivo
    ContributorAuthor

    Done — 0735b24 (API) + 91e762c (console). CI green on 91e762c; both deploys succeeded.

    What moved, what stayed

    Account (Preferences → Connections): connect / reconnect / disconnect for Gmail, Google Drive and Zoho WorkDrive; the GitHub identity link; and the link callback's ?github_linked= effect, which had to follow the button or its confirmation lands on a page nobody is on.

    Instance (Settings → Permissions & Connections): the per-agent folder grant, and the "allow this agent to read my inbox" checkbox. Both are genuinely per-agent and were never the leak.

    Two gating changes go with it:

    • The Drive/WorkDrive grant panel now renders on connected, not configured — a grant against an account you have not connected is the dead row [bug] An unconfigured connector renders a red error on every agent's Settings tab — Zoho WorkDrive has never been configured #353 removed, wearing a different hat. So on this deployment the block is gone from all 26 instances, which is the reported symptom.
    • The Gmail checkbox is deliberately still shown, disabled, while Gmail is disconnected. Per the design's "small honesty fix": agent-think.ts:638 offers find_confirmation_link on the permission flag alone, so hiding the checkbox would remove the only control that turns off something still set. That honesty fix is still open — gating the tool on the connection is a separate change, and until it lands the checkbox has to stay reachable.

    The new subtitle

    The design was right that the destination already broke its own promise, so it is rewritten in the same change:

    Your account — connections, timezone, and how you speak, hear and read. Connections and timezone apply to every agent and cannot be overridden. Voice and translation are defaults any agent can override on its own Settings tab. Appearance is this browser only.

    Every section's scope is now stated, including the two that were already wrong (Timezone has no override; Appearance is localStorage). The page is the definition of account scope rather than a page that happens to hold some.

    Did Stage 1 make it a .map()

    Mostly yes, with two additions to GET /v1/connectors that Stage 1 had left out — both of which would otherwise have become a fourth hardcoded per-connector table in the console, which is the arrangement that route exists to end:

    • reach — what a disconnect revokes, per connector, as one GROUP BY over instance_connector_grants alongside the existing user_api_keys read. Disconnecting Drive/WorkDrive leaves every instance's folder grants, and reconnecting silently restores them #357 gave the confirmation its count, but from a per-connector status route; a page listing every connector needs it for every row. null (not {grants:0}) for a user-model connector, because Gmail's reach is a per-agent flag and a zero would read as "nothing uses this".
    • flow — which connect/disconnect endpoints are live. Drive, Gmail and WorkDrive keep dedicated flows (their OAuth apps register those redirect URIs); every other oauth connector gets the generic pair. Server-side, so Stage 2 retires a dedicated flow by deleting one line rather than hunting for the buttons pointing at it.

    Also credentialEnv: WorkDrive declares no oauth block (per-DC endpoints), so the catalog reported it unconfigured while /v1/workdrive/status reported it connectable. Two answers to "can I connect this?" is what the catalog is for.

    Result: no connector is named anywhere in the console. The one hand-written row is GitHub — its "connection" is an identity link plus an App installation, not a vault row, so folding it into the list would make connected mean two things. It is on this page anyway, per the design.

    Not built, on purpose

    No "Use my defaults / Customise for this agent" for connections (it would promise a per-agent scoping that cannot exist); no generalised scope-badge component (it would repeal the rule this move establishes); no folding connections into /v1/preferences; no #305 split in this change — though SettingsTab.tsx did drop 1169 → 1015 lines and the file-size ratchet pin was lowered to record it, so the ground is held.

    Corrections confirmed: the tables are user_api_keys and instance_connector_grants; connector_tokens/connector_grants do not exist.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestfrontendFrontend / UI work

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions