Skip to content

fix(settings): stop one member's agent settings reconfiguring the org - #241

Draft
jlav wants to merge 7 commits into
mainfrom
jl/scope-member-settings-save
Draft

fix(settings): stop one member's agent settings reconfiguring the org#241
jlav wants to merge 7 commits into
mainfrom
jl/scope-member-settings-save

Conversation

@jlav

@jlav jlav commented Aug 25, 2026

Copy link
Copy Markdown
Member

HUMAN:

  • A human has tested these changes.

AGENT:


Why

On a shared install, one member's settings were reconfiguring everyone else. Two things went wrong, both entered through /settings/agent, both reachable by a plain member.

A member switching to codex pushed that choice onto the org default and onto every other member's row. Everyone without codex credentials then got a 400 on every new conversation. This blocked all 17 members of the beta staging instance.

Separately, saving that page detached a member from org-wide defaults. An admin would change the org's model and it reached only the members who had never saved their agent settings. Nothing signalled this in either direction.

Both come from the same mistake. A member's row is meant to hold only their overrides, with everything else resolving through the org. But the save path receives their composed settings, which is the org defaults with their edits already merged in, and it treated every value in there as a deliberate choice. So it persisted values the member never made, outward onto other rows and inward onto their own.

Approach

Write the acting member's row only, and store only what they actually chose.

Separating a real choice from an inherited one takes two checks, because two different things can fake one:

  • The SDK invents values. Changing agent_kind rebuilds the variant from SDK defaults, so every field the caller didn't send comes back fabricated. Settings.update() now records what the caller actually supplied on a flip.
  • Inherited values look ordinary. Anything else the member never touched is just the org's value passing through. Comparing against org.agent_settings is what identifies it.

Neither check covers the other. A fabricated model genuinely differs from the org's, so the comparison keeps it. An ordinary save fabricates nothing, so the flip check never fires.

Org-wide defaults get their own way in: POST /orgs/{id}/app takes an agent_settings_diff, gated on EDIT_ORG_SETTINGS.

Issue Number

OHE-3138 - https://linear.app/all-hands-ai/issue/OHE-3138

How to Test

cd enterprise && poetry run pytest tests/unit/test_saas_settings_store.py

With an admin and a member in one org:

  1. As the member, switch to ACP + codex on /settings/agent and save. The admin should still be able to start a conversation.
  2. As the member, switch back and start a conversation without picking a model. It should use the org's model.
  3. As the member, hit Save on /settings/agent. Change the org's default model as admin. The member should pick it up.

Video/Screenshots

Type

  • Bug fix
  • Feature
  • Refactor
  • Breaking change
  • Docs / chore

Notes

Merge #254 first. It carries the first two commits here on their own, which is the minimum needed to stop one member reconfiguring the org. This PR adds the two follow-on fixes on top. Once #254 lands I'll rebase and the shared commits drop out.

No migration. A row full of values the member never chose thins out on their next save.

Three follow-ups I left out:

  • store_settings has no role check, so POST /api/v1/settings accepts an LLM payload from a member the UI correctly treats as read-only. That's a permissions gap rather than part of this fix.
  • A harness switch falls back to the org default rather than restoring the member's own previous model. That's the snapshot behaviour in ACP settings: preserve LLM/condenser/MCP config across OpenHands ↔ ACP toggles OpenHands#14370.
  • agent_context.current_datetime regenerates on every construction, so it lands in a member's row on each save. The OpenHands agent rebuilds the context at conversation start and discards it, but an ACP harness can read the stored one and be told a stale current date.

Enterprise server image for this PR:

ghcr.io/openhands/enterprise-server:sha-3deff29

@github-actions github-actions Bot added the type: fix A bug fix label Aug 25, 2026
@linear

linear Bot commented Aug 25, 2026

Copy link
Copy Markdown
OHE-3138 Changing your own agent settings changes them for everyone in the org

What happens

If you change your own agent settings - switching your agent from OpenHands to Codex, say, or picking a different model - that change is applied to every other member of your organization, and to the organization's defaults.

There's no warning that this will happen, and you don't need to be an admin or owner to do it. Any member can.

For everyone else, their agent silently becomes whatever you picked. If your choice needs credentials they don't have, such as a connected Codex account or an API key, then every new conversation they try to start fails with an error like:

Connect your Codex account or set an API key before starting a Codex conversation.

They can't fix it for themselves either. If they change their own settings back, that just pushes their choice onto everyone else in turn.

What we saw

On our shared beta instance, one person switched their agent to Codex to try it out. That immediately blocked all 17 members of the organization from starting any new conversation, and it stayed broken until someone changed the setting back.

Switching the agent also wipes the organization's configured model and connection URL, and switching back doesn't restore them. So recovering isn't just a matter of undoing the change - someone has to know the original values and re-enter them by hand.

Expected behavior

  • Changing your own agent settings should only affect you.
  • Only an admin or owner should be able to change what the whole organization defaults to, and it should be obvious that's what they're doing.
  • Someone trying out a different agent shouldn't be able to stop their teammates from working.
  • Undoing a change should restore what was there before.

Impact

Any organization with more than one member. A single member with no special permissions can stop everyone else from starting conversations by changing a setting on their own account, and the people affected have no way to recover on their own.

Fix

The first three points are addressed in #241.

Restoring the model and connection URL after an agent switch is a separate gap, tracked upstream in OpenHands/OpenHands#14370.

Review in Linear

@github-actions

github-actions Bot commented Aug 25, 2026

Copy link
Copy Markdown

Coverage report

Click to see where and how coverage changed

FileStatementsMissingCoverageCoverage
(new stmts)
Lines missing
  enterprise/server/routes
  org_models.py 688-689, 699-702
  orgs.py 557-566
  enterprise/storage
  org_app_settings_store.py 215-225
  saas_settings_store.py 105, 121-130, 220-221, 231-234, 244-250, 726-751, 769-775, 790, 803-822
  openhands/app_server/settings
  settings_models.py 807-815, 858-867
Project Total  

This report was generated by python-coverage-comment-action

@jlav jlav changed the title fix(settings): scope member settings saves to their own org_member row fix(settings): stop one member's agent settings reconfiguring the org Aug 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type: fix A bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant