Skip to content

[bug] Whole-blob writes to agent_instances.config lose concurrent updates (16 call sites) #231

Description

@serge-ivo

Why

agent_instances.config is one JSON blob, and 16 call sites read it whole, mutate one key,
and write it whole:

routes/instances-apply.ts · routes/instances-translation.ts · routes/tools.ts
routes/instances-behaviour.ts · routes/coding.ts · routes/instances.ts
lib/behaviour-store.ts · lib/board.ts

Two concurrent requests touching different keys therefore lose one of them: both read the
same pre-state, and the second write clobbers the first.

This is not hypothetical — it already shipped a user-visible bug. The Behaviour tab's "Reset
group" fired one PUT per field; all five read the same config and the last write won, so it
cleared exactly one field, apparently at random. Fixed for that one caller (5365774) by batching
into a single request, but the underlying pattern is untouched and every other caller has it.

Realistic collisions today, none needing an unusual race:

  • The agent calls set_behaviour (or configure_board, or a settings write) while the owner
    saves something in the console.
  • A trigger-driven pipeline writes config while the owner edits Settings.
  • Two browser tabs open on the same instance.

The loss is silent — no error, no conflict, the setting just doesn't stick.

Options

  1. Optimistic concurrency — add a config_version column, UPDATE … WHERE config_version = ?, retry on 0 rows. Correct, one migration, every write site changes.
  2. Single-key writes in SQLjson_set(config, '$.behaviour', ?) so a write only touches
    its own subtree. No migration; each site becomes a targeted patch. Doesn't help two writers of
    the same key, which is the rarer and less harmful case.
  3. One helper all sites must go through (patchInstanceConfig(env, id, uid, key, value)),
    implementing whichever of the above is chosen, plus a lint/test forbidding raw
    UPDATE agent_instances SET config elsewhere.

(3) + (2) is probably the cheapest correct answer.

Verification

  • A test that fires two concurrent patches to different keys and asserts BOTH survive. It must
    fail against today's code — otherwise it isn't reproducing the bug.

Activity

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

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions