Skip to content

Commit 88ecec5

Browse files
committed
fix(serve): invoke GraphBootstrapper directly instead of via dead ApplicationReadyEvent listener
GraphBootstrapper's H2->Neo4j fallback was registered for @eventlistener(ApplicationReadyEvent.class), but that event never fires during a normal serve run because ServeCommand.call() blocks in Thread.join() as a CommandLineRunner. Consequence: if someone shipped a bundle with only the H2 analysis cache (no enriched Neo4j graph) and ran `code-iq serve`, Neo4j would stay empty forever and graph-traversal queries would return nothing. Not observed in our pipeline today only because `enrich` always runs before `serve`. Fix: - Drop @eventlistener from bootstrapNeo4jFromCache() and its associated imports (ApplicationReadyEvent, @eventlistener). - ServeCommand.call() now calls graphBootstrapper.bootstrapNeo4jFromCache() explicitly before the Neo4j status report, so the advertised node/edge counts reflect post-bootstrap state. Injection is Optional since the bean only exists in the "serving" profile (matches the existing @Profile + @ConditionalOnBean guards). - Class javadoc updated to document the new invocation model and the reason the event-listener path did not work. The method is idempotent (guards on count>0 and on missing H2 file), so moving the invocation is safe even in scenarios where Neo4j is already populated by `enrich`. Tests: existing GraphBootstrapperTest still passes unchanged (it calls the method directly). Integration verification: # full pipeline to populate both H2 + Neo4j ./scripts/baseline/run-pipeline.sh spring-petclinic # wipe ONLY Neo4j; keep H2 rm -rf .seeds/spring-petclinic/.code-iq/graph # re-run serve java -jar target/code-iq-*-cli.jar serve .seeds/spring-petclinic --port 18080 & # wait for /actuator/health=200, then: curl http://127.0.0.1:18080/api/stats Before this fix: Neo4j empty, stats=0. After this fix: log shows "Bootstrapped Neo4j with 634 nodes and 604 edges from H2 cache", /api/stats returns 677 nodes / 604 edges / 65 files.
1 parent 3beafd9 commit 88ecec5

2 files changed

Lines changed: 21 additions & 4 deletions

File tree

src/main/java/io/github/randomcodespace/iq/cli/ServeCommand.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.github.randomcodespace.iq.cli;
22

33
import io.github.randomcodespace.iq.config.CodeIqConfig;
4+
import io.github.randomcodespace.iq.config.GraphBootstrapper;
45
import io.github.randomcodespace.iq.graph.GraphStore;
56
import org.slf4j.Logger;
67
import org.slf4j.LoggerFactory;
@@ -63,6 +64,10 @@ public class ServeCommand implements Callable<Integer> {
6364
@Autowired
6465
private ApplicationEventPublisher events;
6566

67+
// Optional: only present in the "serving" profile (same conditions as the bean).
68+
@Autowired(required = false)
69+
private GraphBootstrapper graphBootstrapper;
70+
6671
@Override
6772
public Integer call() {
6873
Path root = path.toAbsolutePath().normalize();
@@ -72,6 +77,15 @@ public Integer call() {
7277
}
7378
NumberFormat nf = NumberFormat.getIntegerInstance(Locale.US);
7479

80+
// Bootstrap Neo4j from the H2 analysis cache if Neo4j is empty. This is
81+
// a no-op when enrich has already run (guarded internally by a count>0
82+
// check) and when the H2 cache file is missing. Must happen before the
83+
// status report below so the advertised node/edge counts are truthful.
84+
// See GraphBootstrapper javadoc for why this is not an @EventListener.
85+
if (graphBootstrapper != null) {
86+
graphBootstrapper.bootstrapNeo4jFromCache();
87+
}
88+
7589
// Report Neo4j graph status
7690
if (graphStore != null) {
7791
try {

src/main/java/io/github/randomcodespace/iq/config/GraphBootstrapper.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,7 @@
77
import org.slf4j.Logger;
88
import org.slf4j.LoggerFactory;
99
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
10-
import org.springframework.boot.context.event.ApplicationReadyEvent;
1110
import org.springframework.context.annotation.Profile;
12-
import org.springframework.context.event.EventListener;
1311
import org.springframework.stereotype.Component;
1412

1513
import java.nio.file.Files;
@@ -25,7 +23,13 @@
2523
* {@code serve} (which needs Neo4j for graph traversal queries like shortest path,
2624
* cycles, impact trace, ego graph, and neighbors).
2725
* <p>
28-
* Runs after the Spring context is fully ready, so Neo4j and GraphStore are available.
26+
* Invoked explicitly by {@link io.github.randomcodespace.iq.cli.ServeCommand}
27+
* before the server reports "Server started", so the advertised node/edge counts
28+
* reflect the bootstrapped state. Previously triggered via
29+
* {@code @EventListener(ApplicationReadyEvent.class)}, but that event never fires
30+
* because {@code ServeCommand.call()} blocks (as a {@code CommandLineRunner}) and
31+
* Spring's ready-event publication waits for runners to return.
32+
* <p>
2933
* Only active in the "serving" profile when GraphStore is present.
3034
*/
3135
@Component
@@ -43,7 +47,6 @@ public GraphBootstrapper(GraphStore graphStore, CodeIqConfig config) {
4347
this.config = config;
4448
}
4549

46-
@EventListener(ApplicationReadyEvent.class)
4750
public void bootstrapNeo4jFromCache() {
4851
// Skip bootstrap in read-only mode (no writes allowed)
4952
if (config.isReadOnly()) {

0 commit comments

Comments
 (0)