@@ -66,6 +66,31 @@ func openStoreByBackend(cfg *config.Config, backend, storeDir, adapter string) (
6666 }
6767}
6868
69+ // openStoreStrict opens a store without fallback. Unlike openStoreByBackend,
70+ // a greptimedb failure returns an error instead of silently falling back to
71+ // the file store. This is appropriate for reader paths (drain / MCP) where
72+ // falling back would silently drain the wrong data.
73+ func openStoreStrict (cfg * config.Config , backend , storeDir , adapter string ) (store.Store , error ) {
74+ switch backend {
75+ case "" , "file" :
76+ return filestore .New (storeDir , adapter )
77+ case "greptimedb" :
78+ gs , err := greptimestore .New (cfg .Store .GreptimeDB , storeDir , adapter )
79+ if err != nil {
80+ return nil , fmt .Errorf ("greptimedb: %w" , err )
81+ }
82+ ctx , cancel := context .WithTimeout (context .Background (), 3 * time .Second )
83+ defer cancel ()
84+ if err := gs .Ping (ctx ); err != nil {
85+ _ = gs .Close ()
86+ return nil , fmt .Errorf ("greptimedb ping: %w" , err )
87+ }
88+ return gs , nil
89+ default :
90+ return nil , fmt .Errorf ("unknown store backend: %q" , backend )
91+ }
92+ }
93+
6994// resolveDrainSources returns 1 or 2 drain sources for the MCP server and drain command.
7095//
7196// - "configured" source: from CLI --store/--session flags (or global config defaults)
@@ -108,10 +133,10 @@ func resolveDrainSources(cmd *cobra.Command) ([]mcp.DrainSource, func(), error)
108133 }
109134 }
110135
111- configuredStore , err := openStoreByBackend ( cfg , configuredBackend , storeDir , adapterName )
112- if err != nil {
113- return nil , nil , fmt . Errorf ( "open configured store: %w" , err )
114- }
136+ // Use strict open for the configured store so that a greptimedb failure
137+ // is surfaced rather than silently falling back to the local file store
138+ // (which would drain wrong data). On failure, degrade to local-only.
139+ configuredStore , configuredErr := openStoreStrict ( cfg , configuredBackend , storeDir , adapterName )
115140
116141 // Resolve "local" source: always use global config backend + auto-detected session.
117142 localBackend := cfg .Store .Backend
@@ -128,6 +153,20 @@ func resolveDrainSources(cmd *cobra.Command) ([]mcp.DrainSource, func(), error)
128153 return b
129154 }
130155
156+ // Configured store unavailable — fall back to local-only single source.
157+ if configuredErr != nil {
158+ fmt .Fprintf (os .Stderr , "devtap: configured store %q unavailable (%v), using local only\n " ,
159+ configuredBackend , configuredErr )
160+ localStore , err := openStoreByBackend (cfg , localBackend , storeDir , adapterName )
161+ if err != nil {
162+ return nil , nil , fmt .Errorf ("open local store: %w" , err )
163+ }
164+ cleanup := func () { _ = localStore .Close () }
165+ return []mcp.DrainSource {
166+ {Store : localStore , SessionID : localSession , Label : localSession },
167+ }, cleanup , nil
168+ }
169+
131170 // If both sources resolve to the same (backend, session), single source.
132171 if normBackend (configuredBackend ) == normBackend (localBackend ) && configuredSession == localSession {
133172 cleanup := func () { _ = configuredStore .Close () }
0 commit comments