From a85e5727324caafe9eca03c30851fff40eeba472 Mon Sep 17 00:00:00 2001 From: Hein Date: Mon, 20 Jul 2026 13:45:13 +0200 Subject: [PATCH] fix(hooks): reset Tx to pooled connection for post-commit hook calls BeforeScan (restheadspec handleUpdate) and BeforeResponse (funcspec list/single query handlers) fire after RunInTransaction commits, but hookCtx.Tx still pointed at the now-dead transaction. Any hook that executed a query against Tx (e.g. setUserViaContext) failed with "sql: transaction has already been committed or rolled back". --- pkg/funcspec/function_api.go | 10 ++++++++-- pkg/restheadspec/handler.go | 3 +++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/pkg/funcspec/function_api.go b/pkg/funcspec/function_api.go index fbb8d30..0773e63 100644 --- a/pkg/funcspec/function_api.go +++ b/pkg/funcspec/function_api.go @@ -331,7 +331,10 @@ func (h *Handler) SqlQueryList(sqlquery string, options SqlQueryOptions) HTTPFun w.Header().Set("Content-Range", fmt.Sprintf("items %d-%d/%d", respOffset, respOffset+len(dbobjlist), total)) logger.Info("Serving: Records %d of %d", len(dbobjlist), total) - // Execute BeforeResponse hook + // Execute BeforeResponse hook. The transaction has already committed by + // this point, so hooks must use the pooled connection rather than the + // now-dead tx. + hookCtx.Tx = h.db hookCtx.Result = dbobjlist hookCtx.Total = total if err := h.hooks.Execute(BeforeResponse, hookCtx); err != nil { @@ -631,7 +634,10 @@ func (h *Handler) SqlQuery(sqlquery string, options SqlQueryOptions) HTTPFuncTyp return } - // Execute BeforeResponse hook + // Execute BeforeResponse hook. The transaction has already committed by + // this point, so hooks must use the pooled connection rather than the + // now-dead tx. + hookCtx.Tx = h.db hookCtx.Result = dbobj if err := h.hooks.Execute(BeforeResponse, hookCtx); err != nil { logger.Error("BeforeResponse hook failed: %v", err) diff --git a/pkg/restheadspec/handler.go b/pkg/restheadspec/handler.go index 9926ef9..8d87fa6 100644 --- a/pkg/restheadspec/handler.go +++ b/pkg/restheadspec/handler.go @@ -1498,6 +1498,9 @@ func (h *Handler) handleUpdate(ctx context.Context, w common.ResponseWriter, id // Execute BeforeScan hooks so row security is re-applied to the post-update // re-fetch, same as it is for the initial read and the update query itself. // Without this, the re-fetch can return a row the caller isn't authorized to see. + // The transaction has already committed by this point, so hooks must use the + // pooled connection rather than the now-dead tx. + hookCtx.Tx = h.db hookCtx.Query = selectQuery if err := h.hooks.Execute(BeforeScan, hookCtx); err != nil { logger.Error("BeforeScan hook failed: %v", err)