()
| 161 | * @returns true if versions match, false if versions differ or no runner running |
| 162 | */ |
| 163 | export async function isRunnerRunningCurrentlyInstalledHappyVersion(): Promise<boolean> { |
| 164 | logger.debug('[RUNNER CONTROL] Checking if runner is running same version'); |
| 165 | const runningRunner = await checkIfRunnerRunningAndCleanupStaleState(); |
| 166 | if (!runningRunner) { |
| 167 | logger.debug('[RUNNER CONTROL] No runner running, returning false'); |
| 168 | return false; |
| 169 | } |
| 170 | |
| 171 | const state = await readRunnerState(); |
| 172 | if (!state) { |
| 173 | logger.debug('[RUNNER CONTROL] No runner state found, returning false'); |
| 174 | return false; |
| 175 | } |
| 176 | |
| 177 | const settings = await readSettings(); |
| 178 | const currentApiUrl = process.env.HAPI_API_URL |
| 179 | || settings.apiUrl |
| 180 | || settings.serverUrl |
| 181 | || configuration.apiUrl; |
| 182 | const currentCliApiToken = process.env.CLI_API_TOKEN |
| 183 | || settings.cliApiToken |
| 184 | || configuration.cliApiToken; |
| 185 | const currentMachineId = settings.machineId; |
| 186 | |
| 187 | try { |
| 188 | // When HAPI_DISABLE_VERSION_HANDOFF=1 is set on the live runner (operator |
| 189 | // owns supervision via systemd/tmux/custom rebuild pipelines), a fresh CLI invocation must NOT |
| 190 | // treat the running runner as stale just because source mtimes shifted. |
| 191 | // Otherwise `hapi runner start` would kill the live runner mid-rebuild. |
| 192 | // |
| 193 | // Per Codex review #814 [Major]: the env var is commonly only set on the |
| 194 | // supervising service (systemd unit file), NOT on the operator's |
| 195 | // interactive shell. To honour the running runner's opt-out from any |
| 196 | // caller, OR the live env-var check with the persisted-at-start snapshot |
| 197 | // in state.startedWithVersionHandoffDisabled. |
| 198 | if ( |
| 199 | process.env.HAPI_DISABLE_VERSION_HANDOFF === '1' |
| 200 | || state.startedWithVersionHandoffDisabled === true |
| 201 | ) { |
| 202 | logger.debug('[RUNNER CONTROL] Version handoff disabled (env or persisted state), skipping mtime/version drift check'); |
| 203 | } else { |
| 204 | const currentCliMtimeMs = getInstalledCliMtimeMs(); |
| 205 | if (typeof currentCliMtimeMs === 'number' && typeof state.startedWithCliMtimeMs === 'number') { |
| 206 | logger.debug(`[RUNNER CONTROL] Current CLI mtime: ${currentCliMtimeMs}, Runner started with mtime: ${state.startedWithCliMtimeMs}`); |
| 207 | if (currentCliMtimeMs !== state.startedWithCliMtimeMs) { |
| 208 | return false; |
| 209 | } |
| 210 | } else { |
| 211 | const currentCliVersion = packageJson.version; |
| 212 | logger.debug(`[RUNNER CONTROL] Current CLI version: ${currentCliVersion}, Runner started with version: ${state.startedWithCliVersion}`); |
| 213 | if (currentCliVersion !== state.startedWithCliVersion) { |
| 214 | return false; |
| 215 | } |
| 216 | } |
| 217 | } |
| 218 | |
| 219 | const currentIdentityMatches = isRunnerStateCompatibleWithIdentity(state, { |
| 220 | apiUrl: currentApiUrl, |
no test coverage detected