You are mid-request. You want to kick off some best-effort work (a metric flush, a cache warm, an audit log write) that should outlive the HTTP handler. You do not want to use context.Background() because then you lose your request id, trace span, and auth. But you also do not want the request’s cancellation to kill your background work.

Go 1.21 added context.WithoutCancel, which is exactly this. For older codebases, the trick is a custom context that delegates Value but returns zero for Done and Err.

type detached struct{ parent context.Context }

func (d detached) Deadline() (time.Time, bool) { return time.Time{}, false }
func (d detached) Done() <-chan struct{}       { return nil }
func (d detached) Err() error                  { return nil }
func (d detached) Value(k any) any             { return d.parent.Value(k) }

// Detach returns a context that inherits values from parent but is never
// cancelled or deadlined.
func Detach(parent context.Context) context.Context {
    return detached{parent: parent}
}

// Usage:
go func() {
    ctx, cancel := context.WithTimeout(Detach(r.Context()), 30*time.Second)
    defer cancel()
    if err := flushMetrics(ctx, event); err != nil {
        log.Printf("metrics flush: %v", err)
    }
}()

Always put your own timeout on top, otherwise the goroutine can outlive your process restart. See also /posts/context-context-is-not-a-cache/.