Skip to content

Turn screenshots are written in every deployment and swept in one #263

Description

@zopeVaibhav

computer_page_frame holds one screenshot per page a Bot opens, up to 4 MB each, so a conversation read back later shows what it was looking at. page-frames.ts:57-59 says why they need a reaper: "a Bot that browses grows this table for as long as it runs and nothing ever took anything out of it. Screenshots are the largest thing this deployment stores."

That reaper exists and works. It has one caller, and that caller runs in one deployment shape out of four.

What it looks like

The only caller is server/scripts/cull-idle-computers.ts:87. Run it under each shape this repository ships:

Compose (docker/supervisor)   error: The culler only has something to do where each Bot has its own
                              computer, and this deployment uses the "docker" provider.
all-in-one (embedded)         error: ... uses the "shared" provider.
helm computers.mode: shared   error: ... uses the "shared" provider.
helm computers.mode: sandbox  (runs)

charts/openbot/values.yaml:178 sets computers.mode: shared, and four of the five shipped ci/ targets use it. grep -rn "cull\|sweep\|purge" docker-compose.yml docker/s6/ returns nothing, so neither Compose nor the all-in-one image runs it either.

What that costs, against a real database, one Bot browsing forty pages a day for ninety days at 200 KB a frame:

  rows: 3600   table: 734 MB

None of it will ever be removed in those three deployments.

Why it happens

The write path is ungated: server/src/index.ts builds the store unconditionally and hands it to the gateway and to createApp, so every deployment writes frames.

The removal path is gated twice. cull-idle-computers.ts:31 throws unless the provider is sandbox, and the only thing that schedules the script is charts/openbot/templates/computer/culler-cronjob.yaml:1, which renders only in that mode.

The placement was deliberate, and the script says why:

Here rather than in the API server because this is already the sweep that runs on a schedule with a claim under it, and a second timer would be a second thing to get wrong.

The reasoning is sound. The consequence was that the sweep exists in one mode and the frames are written in all of them.

Reproduction

  1. Point DATABASE_URL at a migrated database, write a frame through createPageFrameStore(...).save(...), and age its captured_at past thirty days.
  2. Run bun scripts/cull-idle-computers.ts with COMPUTER_SUPERVISOR_URL set, which is the Compose shape. It refuses with the message above; the row remains.
  3. Run the server with AUDIT_RETENTION_DAYS unset, which is the default, and leave it. The row remains, because nothing in the server touches this table.

Why it matters

These are the largest rows the product stores, and the deployments that never sweep them are the ones most likely to be on a single disk: Compose on somebody's box, and the all-in-one image with EMBEDDED_POSTGRES=on, where the database is inside the container. Nothing reports it — no log line, no metric — so it is invisible until somebody looks at disk usage or a backup starts overrunning its window.

What a fix probably has to do

The sweep has to live somewhere every deployment has, and the server is the only such place: the culler is Kubernetes-only by construction and Compose has no scheduler at all. The server already runs this exact shape for the audit trail — an hourly unref'd timer with an advisory lock so one replica sweeps — so frames could ride that timer rather than starting the second one the script was right to avoid.

One decision worth making rather than inheriting: audit retention is off unless AUDIT_RETENTION_DAYS is set, because deleting a trail on a default would be worse than keeping it. Screenshots are not the trail — the store already chose thirty days and the sandbox deployments have been enforcing it — so whether the frame sweep is on by default is a separate question, and answering it by reusing the audit flag would leave most deployments where they are now.

Severity

Nothing is broken and no boundary is crossed. It is unbounded growth in the table holding the largest objects in the product, in three of the four ways this repository can be deployed, with the fix already written and called from one place.

Not urgent for anyone on computers.mode: sandbox, which has been sweeping correctly all along.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions