fix(go): read the cookie key before writing it, so a wake costs no write

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Paul Campbell pushed by claude agent, operated by paul@keymail.dev d9bf9fcbd325f2ab4aea1722420a708aa1ef06ca parent 09eb61c
2 files changed, +29 −2
  • apps/go/app_test.go +20 −0
  • apps/go/store.go +9 −2
diff --git a/apps/go/app_test.go b/apps/go/app_test.go
index 21d61b2..2c5cde9 100644
--- a/apps/go/app_test.go
+++ b/apps/go/app_test.go
@@ -310,3 +310,23 @@ func TestMigrationsAreRepeatable(t *testing.T) {
s.Close()
}
}
+
+func TestCookieKeySurvivesRestart(t *testing.T) {
+ path := filepath.Join(t.TempDir(), "k.db")
+ var keys [][]byte
+ for range 2 {
+ s, err := openStore(path)
+ if err != nil {
+ t.Fatal(err)
+ }
+ k, err := s.Secret(t.Context())
+ if err != nil || len(k) != 32 {
+ t.Fatalf("key %x, %v", k, err)
+ }
+ keys = append(keys, k)
+ s.Close()
+ }
+ if !bytes.Equal(keys[0], keys[1]) {
+ t.Error("cookie key changed across restarts, so every visitor would lose their list")
+ }
+}
diff --git a/apps/go/store.go b/apps/go/store.go
index 85354c6..da969d7 100644
--- a/apps/go/store.go
+++ b/apps/go/store.go
@@ -113,12 +113,19 @@ func (s *Store) Ping(ctx context.Context) error { return s.r.PingContext(ctx) }
// Secret returns the instance's cookie-signing key, creating it on first use.
func (s *Store) Secret(ctx context.Context) ([]byte, error) {
- key := make([]byte, 32)
+ var key []byte
+ err := s.r.QueryRowContext(ctx, `SELECT value FROM meta WHERE name = 'cookie_key'`).Scan(&key)
+ if err == nil {
+ return key, nil
+ } else if !errors.Is(err, sql.ErrNoRows) {
+ return nil, err
+ }
+ key = make([]byte, 32)
rand.Read(key)
if _, err := s.w.ExecContext(ctx, `INSERT INTO meta (name, value) VALUES ('cookie_key', ?) ON CONFLICT DO NOTHING`, key); err != nil {
return nil, err
}
- err := s.w.QueryRowContext(ctx, `SELECT value FROM meta WHERE name = 'cookie_key'`).Scan(&key)
+ err = s.w.QueryRowContext(ctx, `SELECT value FROM meta WHERE name = 'cookie_key'`).Scan(&key)
return key, err
}