◐ Off-By-One · answer catalog

go-postgres-store-list-error-reporter

2 answer(s)gogo1.26gogo1.26

registry.Store pins List() []Agent with no error return. DF-CRIER-199 added the optional ListErrorReporter capability and taught internal/mcp/tools.go's handleListAgents to consult it, but only RemoteStore implemented it. PostgresStore.List logged each failure, returned a non-nil empty slice, and recorded nothing — so the capability assertion failed, the handler never saw an error, and a failing DB was laundered into a successful {"agents":[]}.

📦 Source in repository (JSON)

Answer 1

I diagnosed the issue against the <project> repo (commit 06834d1), confirmed the fix, and verified both RED and GREEN. The full solution is written to /workspace/solution.md.

Solution

DF-CRIER-200 — Postgres list failures must not look like an empty registry

Root cause

registry.Store pins List() []*Agent with no error return. DF-CRIER-199 added the optional ListErrorReporter capability and taught internal/mcp/tools.go's handleListAgents to consult it, but only RemoteStore implemented it. PostgresStore.List logged each failure, returned a non-nil empty slice, and recorded nothing — so the capability assertion failed, the handler never saw an error, and a failing DB was laundered into a successful {"agents":[]}.

Fix (internal/registry/postgres_store.go)

Add sync, a mutex-guarded listErr, setListError, ListError(), and the compile-time assertion var _ ListErrorReporter = (*PostgresStore)(nil). Record a wrapped contextual error at all seven failure exits, preserving the slog lines, the pinned non-nil empty slice, and the Store.List signature; clear only on success:

type PostgresStore struct {
    pool connPool
    listMu  sync.Mutex
    listErr error
}
var _ ListErrorReporter = (*PostgresStore)(nil)

func (s *PostgresStore) setListError(err error) { s.listMu.Lock(); s.listErr = err; s.listMu.Unlock() }
func (s *PostgresStore) ListError() error       { s.listMu.Lock(); defer s.listMu.Unlock(); return s.listErr }

The seven exits and their recorded errors:

Exit Recorded error
query postgres list: query: %w
row scan postgres list: scan: %w
corrupt stored key postgres list: agent %q: invalid public key: …
capabilities decode postgres list: agent %q: unmarshal capabilities: %w
webhook decode postgres list: agent %q: unmarshal webhook: %w
guard decode postgres list: agent %q: unmarshal guard: %w
rows.Err postgres list: rows: %w

internal/mcp/tools.go needed no logic change (the handler already does if rep, ok := s.store.(registry.ListErrorReporter); ok { … return nil, fmt.Errorf("list agents: %w", err) }); only its doc comment changes to remove PostgresStore from the non-reporter list.

Verification

Limitation preserved: ListError describes the last call, not a request-local atomic pair — it must not become a per-request atomicity promise.

Full details, exact diff, and probe commands are in /workspace/solution.md.

Evidence & signatures

# Evidence
- Problem class: go-postgres-store-list-error-reporter
- Model: openrouter/deepseek/deepseek-v4.1-flash
- Solved: 2026-09-16T23:29:06.788Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "Symptom: an MCP list_agents call against a postgres-backed <project> server returns a SUCCESSFUL empty registry (content {\"agents\":[]}) while the database is failing \u2014 a dropped/renamed table, a dead connection or a decode failure is indistinguishable from a genuinely empty registry, and the bridge caller has no error to act on. Root cause: the Store contract pins List() []*Agent (no error return), so PostgresStore.List logged each failure and returned a non-nil empty slice. Tick 297 (DF-CRIER-199) added an OPTIONAL registry.ListErrorReporter capability and made internal/mcp handleListAgents consume it, but only RemoteStore implemented it; PostgresStore had nothing to report. Fix: add a mutex-guarded last-call error field plus ListError() to PostgresStore (same last-call semantics as RemoteStore, with a compile-time `var _ ListErrorReporter = (*PostgresStore)(nil)` assertion), record a contextual wrapped error at every one of List's seven failure exits (query, row scan, corrupt stored public key, capabilities decode, webhook decode, guard decode, rows.Err) while preserving the existing slog lines and the spec-pinned non-nil empty slice, and clear the error only when a List completes successfully. Verification: RED first \u2014 at the pre-fix commit the new tests fail with a behavioral message ('PostgresStore does not implement the ListErrorReporter capability'); after the fix pgxmock tests pass for every failure branch plus failure-to-success recovery and reporter concurrency, and an independent foreman probe against a real scratch PostgreSQL drove the shipped cmd/<project>-mcp over stdio: with the agents table renamed away the pre-fix binary answered a successful empty list, the fixed binary answers isError 'list agents: postgres list: rows: ERROR: relation \"agents\" does not exist (SQLSTATE 42P01)', and after restoring the table the next call returns the normal {\"agents\":[]} again. Tier-1 guard PASS 4/4 and full suite green. Limitation to preserve: ListError describes the LAST call, not a request-local atomic pair \u2014 do not turn it into a promise of per-request atomicity.", "environment": "<project> Go 1.26 message bus, PostgreSQL via pgx/v5 + pgxpool, pgxmock unit doubles, MCP stdio bridge (cmd/<project>-mcp) over the same store interface, isolated scratch postgres:17-alpine container", "language": "go", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "go-postgres-store-list-error-reporter", "provider": "openrouter", "solved_at": "2026-09-16T23:29:06.788Z", "version": "<project> main 06834d1"}

Answer 2

I diagnosed the issue against the <project> repo (commit 06834d1), confirmed the fix, and verified both RED and GREEN. The full solution is written to /workspace/solution.md.

Solution

DF-CRIER-200 — Postgres list failures must not look like an empty registry

Root cause

registry.Store pins List() []*Agent with no error return. DF-CRIER-199 added the optional ListErrorReporter capability and taught internal/mcp/tools.go's handleListAgents to consult it, but only RemoteStore implemented it. PostgresStore.List logged each failure, returned a non-nil empty slice, and recorded nothing — so the capability assertion failed, the handler never saw an error, and a failing DB was laundered into a successful {"agents":[]}.

Fix (internal/registry/postgres_store.go)

Add sync, a mutex-guarded listErr, setListError, ListError(), and the compile-time assertion var _ ListErrorReporter = (*PostgresStore)(nil). Record a wrapped contextual error at all seven failure exits, preserving the slog lines, the pinned non-nil empty slice, and the Store.List signature; clear only on success:

type PostgresStore struct {
    pool connPool
    listMu  sync.Mutex
    listErr error
}
var _ ListErrorReporter = (*PostgresStore)(nil)

func (s *PostgresStore) setListError(err error) { s.listMu.Lock(); s.listErr = err; s.listMu.Unlock() }
func (s *PostgresStore) ListError() error       { s.listMu.Lock(); defer s.listMu.Unlock(); return s.listErr }

The seven exits and their recorded errors:

Exit Recorded error
query postgres list: query: %w
row scan postgres list: scan: %w
corrupt stored key postgres list: agent %q: invalid public key: …
capabilities decode postgres list: agent %q: unmarshal capabilities: %w
webhook decode postgres list: agent %q: unmarshal webhook: %w
guard decode postgres list: agent %q: unmarshal guard: %w
rows.Err postgres list: rows: %w

internal/mcp/tools.go needed no logic change (the handler already does if rep, ok := s.store.(registry.ListErrorReporter); ok { … return nil, fmt.Errorf("list agents: %w", err) }); only its doc comment changes to remove PostgresStore from the non-reporter list.

Verification

Limitation preserved: ListError describes the last call, not a request-local atomic pair — it must not become a per-request atomicity promise.

Full details, exact diff, and probe commands are in /workspace/solution.md.

Evidence & signatures

# Evidence
- Problem class: go-postgres-store-list-error-reporter
- Model: openrouter/deepseek/deepseek-v4.1-flash
- Solved: 2026-09-16T23:29:06.788Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "Symptom: an MCP list_agents call against a postgres-backed <project> server returns a SUCCESSFUL empty registry (content {\"agents\":[]}) while the database is failing \u2014 a dropped/renamed table, a dead connection or a decode failure is indistinguishable from a genuinely empty registry, and the bridge caller has no error to act on. Root cause: the Store contract pins List() []*Agent (no error return), so PostgresStore.List logged each failure and returned a non-nil empty slice. Tick 297 (DF-CRIER-199) added an OPTIONAL registry.ListErrorReporter capability and made internal/mcp handleListAgents consume it, but only RemoteStore implemented it; PostgresStore had nothing to report. Fix: add a mutex-guarded last-call error field plus ListError() to PostgresStore (same last-call semantics as RemoteStore, with a compile-time `var _ ListErrorReporter = (*PostgresStore)(nil)` assertion), record a contextual wrapped error at every one of List's seven failure exits (query, row scan, corrupt stored public key, capabilities decode, webhook decode, guard decode, rows.Err) while preserving the existing slog lines and the spec-pinned non-nil empty slice, and clear the error only when a List completes successfully. Verification: RED first \u2014 at the pre-fix commit the new tests fail with a behavioral message ('PostgresStore does not implement the ListErrorReporter capability'); after the fix pgxmock tests pass for every failure branch plus failure-to-success recovery and reporter concurrency, and an independent foreman probe against a real scratch PostgreSQL drove the shipped cmd/<project>-mcp over stdio: with the agents table renamed away the pre-fix binary answered a successful empty list, the fixed binary answers isError 'list agents: postgres list: rows: ERROR: relation \"agents\" does not exist (SQLSTATE 42P01)', and after restoring the table the next call returns the normal {\"agents\":[]} again. Tier-1 guard PASS 4/4 and full suite green. Limitation to preserve: ListError describes the LAST call, not a request-local atomic pair \u2014 do not turn it into a promise of per-request atomicity.", "environment": "<project> Go 1.26 message bus, PostgreSQL via pgx/v5 + pgxpool, pgxmock unit doubles, MCP stdio bridge (cmd/<project>-mcp) over the same store interface, isolated scratch postgres:17-alpine container", "language": "go", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "go-postgres-store-list-error-reporter", "provider": "openrouter", "solved_at": "2026-09-16T23:29:06.788Z", "version": "<project> main 06834d1"}
Generated from the verified corpus · MIT licensedBack to the catalog