Skip to content

Commit 41a686e

Browse files
committed
[GitHub Workflow] Add syncOnStartup configuration #8103
1 parent 3532229 commit 41a686e

4 files changed

Lines changed: 61 additions & 2 deletions

File tree

ai/mcp/server/github-workflow/Server.mjs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import aiConfig from './config.mjs';
66
import logger from './logger.mjs';
77
import HealthService from './services/HealthService.mjs';
88
import RepositoryService from './services/RepositoryService.mjs';
9+
import SyncService from './services/SyncService.mjs';
910
import {listTools, callTool} from './services/toolService.mjs';
1011

1112
/**
@@ -87,7 +88,11 @@ class Server extends Base {
8788
await RepositoryService.fetchAndCacheViewerPermission();
8889
}
8990

90-
// 5. Connect Transport
91+
// 5. Wait for dependent services
92+
// SyncService (singleton) might be performing a startup sync. We wait for it to be ready.
93+
await SyncService.ready();
94+
95+
// 6. Connect Transport
9196
this.transport = new StdioServerTransport();
9297
await this.mcpServer.connect(this.transport);
9398

ai/mcp/server/github-workflow/config.mjs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ const defaultConfig = {
4646
* @type {string}
4747
*/
4848
repo: 'neo',
49+
/**
50+
* Whether to automatically trigger a full sync when the server starts.
51+
* @type {boolean}
52+
*/
53+
syncOnStartup: true,
4954
/**
5055
* Configuration for the issue synchronization service.
5156
*/

ai/mcp/server/github-workflow/services/HealthService.mjs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,14 @@ class HealthService extends Base {
6666
*/
6767
#lastCheckTime = null;
6868

69+
/**
70+
* Promise of the currently executing health check.
71+
* Used for request deduplication to prevent "thundering herd" of gh CLI calls.
72+
* @member {Promise<Object>|null} #healthCheckPromise
73+
* @private
74+
*/
75+
#healthCheckPromise = null;
76+
6977
/**
7078
* The status from the previous health check, used to detect state transitions
7179
* (e.g., recovery from 'unhealthy' to 'healthy') and log meaningful messages.
@@ -238,9 +246,22 @@ class HealthService extends Base {
238246
}
239247
}
240248

249+
// Check for in-flight request (deduplication)
250+
if (this.#healthCheckPromise) {
251+
logger.debug('[HealthService] Joining in-flight health check...');
252+
return this.#healthCheckPromise;
253+
}
254+
241255
// Cache is stale, was unhealthy, or doesn't exist - perform a fresh check
242256
logger.debug('[HealthService] Performing fresh health check');
243-
const health = await this.#performHealthCheck();
257+
258+
// Create the promise and store it
259+
this.#healthCheckPromise = this.#performHealthCheck().finally(() => {
260+
// Always clear the promise when done, success or fail
261+
this.#healthCheckPromise = null;
262+
});
263+
264+
const health = await this.#healthCheckPromise;
244265

245266
// Detect and log meaningful state transitions
246267
// This helps users understand when their fixes (like running `gh auth login`) succeed

ai/mcp/server/github-workflow/services/SyncService.mjs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import aiConfig from '../config.mjs';
22
import Base from '../../../../../src/core/Base.mjs';
33
import logger from '../logger.mjs';
4+
import HealthService from './HealthService.mjs';
45
import IssueSyncer from './sync/IssueSyncer.mjs';
56
import MetadataManager from './sync/MetadataManager.mjs';
67
import ReleaseSyncer from './sync/ReleaseSyncer.mjs';
@@ -34,6 +35,33 @@ class SyncService extends Base {
3435
singleton: true
3536
}
3637

38+
/**
39+
* @returns {Promise<void>}
40+
*/
41+
async initAsync() {
42+
await super.initAsync();
43+
44+
if (aiConfig.syncOnStartup) {
45+
try {
46+
// Ensure the system is healthy before attempting a sync.
47+
// This call is cached/deduplicated by HealthService, so it's cheap if the server
48+
// has already checked it.
49+
const health = await HealthService.healthcheck();
50+
51+
if (health.status === 'healthy') {
52+
logger.info('[SyncService] Starting automatic startup sync...');
53+
await this.runFullSync();
54+
} else {
55+
logger.warn('[SyncService] Skipping startup sync: GitHub CLI is unhealthy.');
56+
}
57+
} catch (error) {
58+
// We strictly catch errors here to ensure that a sync failure (network, API, etc.)
59+
// does not crash the entire service or prevent the server from starting.
60+
logger.error('[SyncService] Startup sync failed:', error.message);
61+
}
62+
}
63+
}
64+
3765
/**
3866
* The main public entry point for the synchronization process.
3967
*

0 commit comments

Comments
 (0)