{"record":{"id":"82f1aeb2fd39d3b9","repo":"alibaba/spring-ai-alibaba","slug":"configagentwatcher-is-already-started","errorCode":null,"errorMessage":"ConfigAgentWatcher is already started","messagePattern":"ConfigAgentWatcher is already started","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"spring-ai-alibaba-studio/src/main/java/com/alibaba/cloud/ai/agent/studio/loader/ConfigAgentWatcher.java","lineNumber":56,"sourceCode":" * <p>The watcher polls for changes at regular intervals rather than using native filesystem events\n * for better cross-platform compatibility.\n */\nclass ConfigAgentWatcher {\n\tprivate static final Logger logger = LoggerFactory.getLogger(ConfigAgentWatcher.class);\n\n\tprivate final Map<Path, ChangeCallback> watchedFolders = new ConcurrentHashMap<>();\n\tprivate final Map<Path, Map<Path, Long>> watchedYamlFiles = new ConcurrentHashMap<>();\n\tprivate final ScheduledExecutorService fileWatcher = Executors.newSingleThreadScheduledExecutor();\n\tprivate volatile boolean started = false;\n\n\t/**\n\t * Starts watching for file changes.\n\t *\n\t * @throws IllegalStateException if the watcher is already started\n\t */\n\tsynchronized void start() {\n\t\tif (started) {\n\t\t\tthrow new IllegalStateException(\"ConfigAgentWatcher is already started\");\n\t\t}\n\n\t\tlogger.info(\"Starting ConfigAgentWatcher\");\n\t\tfileWatcher.scheduleAtFixedRate(this::checkForChanges, 2, 2, TimeUnit.SECONDS);\n\t\tstarted = true;\n\n\t\tRuntime.getRuntime().addShutdownHook(new Thread(this::stop));\n\t\tlogger.info(\n\t\t\t\t\"ConfigAgentWatcher started successfully. Watching {} folders.\", watchedFolders.size());\n\t}\n\n\t/** Stops the file watcher. */\n\tsynchronized void stop() {\n\t\tif (!started) {\n\t\t\treturn;\n\t\t}\n\n\t\tlogger.info(\"Stopping ConfigAgentWatcher...\");","sourceCodeStart":38,"sourceCodeEnd":74,"githubUrl":"https://github.com/alibaba/spring-ai-alibaba/blob/f82da0b50f35744c13968191be2b1cd2452ef550/spring-ai-alibaba-studio/src/main/java/com/alibaba/cloud/ai/agent/studio/loader/ConfigAgentWatcher.java#L38-L74","documentation":"ConfigAgentWatcher.start() throws IllegalStateException when invoked on a watcher instance whose `started` flag is already true. A watcher schedules a repeating file-check task and must only be started once per instance. Starting it again would leak duplicate scheduled tasks that each fire change callbacks.","triggerScenarios":"Calling start() twice on the same ConfigAgentWatcher instance — e.g. calling start() in both a @PostConstruct hook and application-ready listener, restarting the app lifecycle without creating a new watcher, or a retry path re-invoking start().","commonSituations":"Spring bean lifecycle callbacks firing start() more than once; manual re-init code reusing an old watcher object; copy-pasted bootstrap code calling start() in multiple configuration classes.","solutions":["Guard the call: only invoke start() if the watcher has not been started (track a boolean or check the watcher's state)","Create a fresh ConfigAgentWatcher instance instead of restarting the old one","Consolidate bootstrap logic so start() is called from exactly one lifecycle hook","Catch IllegalStateException around start() if double-start is benign in your flow"],"exampleFix":"// before\nwatcher.start(); // called from @PostConstruct and again from onApplicationEvent\n// after\nif (!watcherStarted.getAndSet(true)) {\n    watcher.start();\n}","handlingStrategy":"try-catch","validationCode":"if (watcherStarted) { throw new IllegalStateException(\"watcher already started\"); }","typeGuard":null,"tryCatchPattern":"try {\n    watcher.start();\n} catch (IllegalStateException alreadyStarted) {\n    logger.debug(\"Watcher already running; ignoring duplicate start\");\n}","preventionTips":["Start the watcher from exactly one lifecycle hook (e.g. only @PostConstruct, not also ApplicationReadyEvent)","Track start state in your own AtomicBoolean before calling start()","If restart is needed, build a new ConfigAgentWatcher instead of reusing the old instance"],"tags":["java","illegal-state","lifecycle","double-start"],"backgroundTag":"invalid-state-transition","analyzedSha":"f82da0b50f35744c13968191be2b1cd2452ef550","analyzedAt":"2026-09-09T15:32:42.421Z","contentChangedAt":"2026-09-09T15:32:42.421Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}