{"record":{"id":"bbe111a4205f3482","repo":"gastownhall/beads","slug":"httpapi-this-server-has-no-unit-of-work-provider","errorCode":null,"errorMessage":"httpapi: this server has no unit-of-work provider; it answers from configured issue roles","messagePattern":"httpapi: this server has no unit-of-work provider; it answers from configured issue roles","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/httpapi/server.go","lineNumber":1228,"sourceCode":"}\n\n// WithUOW runs fn inside one unit of work and guarantees the rollback.\n//\n// The close context is DETACHED on purpose. Close sends ROLLBACK on the pinned\n// connection, and the transaction layer POISONS that connection if the send\n// fails (internal/storage/uow/doltserver_tx.go) — go-sql-driver's session reset\n// does not clear an open transaction, so a session that may still be in one\n// must never go back to the pool. Correctness is therefore safe either way, but\n// closing with the request's own canceled context would fail the ROLLBACK\n// immediately and burn one pinned session on every client disconnect. Reads\n// never commit.\n//\n// It is provider-only, and says so rather than dereferencing nil: a\n// roles-backed server has no unit of work to open, and the roles it does hold\n// own their own transactions.\nfunc (s *Server) WithUOW(ctx context.Context, rec *reqInfo, fn func(uow.UnitOfWork) error) error {\n\tif s.provider == nil {\n\t\treturn errors.New(\"httpapi: this server has no unit-of-work provider; it answers from configured issue roles\")\n\t}\n\tstart := time.Now()\n\tuw, err := s.provider.NewUOW(ctx)\n\tif rec != nil {\n\t\trec.uowWait = time.Since(start)\n\t}\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tcloseCtx, cancel := context.WithTimeout(context.WithoutCancel(ctx), uowCloseTimeout)\n\t\tdefer cancel()\n\t\tuw.Close(closeCtx)\n\t}()\n\treturn fn(uw)\n}\n\n// acquire takes a database slot, or gives up. A timed-out wait is ErrBusy, not","sourceCodeStart":1210,"sourceCodeEnd":1246,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/httpapi/server.go#L1210-L1246","documentation":"WithUOW opens a unit of work around fn, but a server configured with issue roles instead of a Provider has no unit of work to open — the roles own their own transactions. Instead of dereferencing a nil provider, WithUOW returns this error stating the server is roles-backed.","triggerScenarios":"Calling s.WithUOV/WithUOW (Server.WithUOW) on a server built with Config without Provider set (roles-backed server). Any request handler or middleware that assumes provider-backed configuration and calls WithUOW will get this error.","commonSituations":"Mixing configuration modes: deployment switched from provider-backed to roles-backed (or vice versa) but a handler still calls WithUOW; embedding the httpapi server in another binary and calling WithUOW unconditionally.","solutions":["Configure the server with a Provider if you need unit-of-work semantics in handlers","Branch on s.provider == nil (or your own config knowledge) and take the roles-backed code path instead of calling WithUOW","Refactor the handler to use the role-specific APIs, which manage their own transactions"],"exampleFix":"// before\nerr := srv.WithUOW(ctx, rec, func(uw uow.UnitOfWork) error { return mutate(uw) })\n// after\nif srv.Provider() == nil {\n    return mutateWithRoles(srv, ctx)\n}\nreturn srv.WithUOW(ctx, rec, func(uw uow.UnitOfWork) error { return mutate(uw) })","handlingStrategy":"type-guard","validationCode":"if srv.Provider() == nil {\n    return errors.New(\"roles-backed server: use role APIs, not WithUOW\")\n}","typeGuard":"func hasProvider(s *httpapi.Server) bool { return s.Provider() != nil }","tryCatchPattern":"err := srv.WithUOW(ctx, rec, fn)\nif err != nil && strings.Contains(err.Error(), \"no unit-of-work provider\") {\n    return fallbackToRolesPath(ctx, srv)\n}","preventionTips":["Decide the database source (Provider vs roles) once in config and carry that decision into handler code","Document handlers that require a Provider and refuse to register them on roles-backed servers","Test both configuration modes so provider-only paths fail loudly in CI"],"tags":["httpapi","uow","configuration","nil-provider"],"backgroundTag":"missing-uow-provider","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}