fix(update): harden auto-update — fire agenthub update directly on tick

The old auto path checked `HEAD..origin/branch` right after kicking off a
background fetch, so it read a stale origin ref and only fired on the SECOND
invocation (and never if the agent sat in one long-running `work`).

Now: when AGENTHUB_AUTO_UPDATE is set, a throttled tick (≤1h) spawns
`agenthub update` directly in the background. update does its OWN fresh fetch
+ reset + build and no-ops when current — so it self-heals on the FIRST
invocation, no stale-ref lag. Notify-only path (no flag) unchanged.

Removed the now-unused cooldown const. 122/122 green. Bump 0.6.0 -> 0.6.1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
chahinebrini 2026-06-28 00:29:59 +02:00
parent 78bbebf435
commit b5f75c6c65
3 changed files with 24 additions and 21 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "agenthub", "name": "agenthub",
"version": "0.6.0", "version": "0.6.1",
"description": "Local coordination layer for AI coding agents", "description": "Local coordination layer for AI coding agents",
"type": "module", "type": "module",
"main": "./dist/index.js", "main": "./dist/index.js",

View File

@ -5,11 +5,10 @@ import { dirname, join, parse } from 'node:path';
import { homedir } from 'node:os'; import { homedir } from 'node:os';
const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
// In auto-update mode we refresh more often so a freshly pushed version is // In auto-update mode we run an actual background `agenthub update` at most this
// picked up within the hour, and we cool down between background updates so we // often (it self-no-ops when current), so a freshly pushed version is picked up
// never spawn a second reset+build while one is still running. // within the hour without spawning competing reset+builds.
const AUTO_CHECK_INTERVAL_MS = 60 * 60 * 1000; const AUTO_CHECK_INTERVAL_MS = 60 * 60 * 1000;
const AUTO_UPDATE_COOLDOWN_MS = 10 * 60 * 1000;
/** Opt-in: set AGENTHUB_AUTO_UPDATE=1 to self-update in the background. */ /** Opt-in: set AGENTHUB_AUTO_UPDATE=1 to self-update in the background. */
function autoUpdateEnabled(): boolean { function autoUpdateEnabled(): boolean {
@ -180,11 +179,26 @@ export function maybeNotifyUpdate(rootOverride?: string): void {
if (!root || !existsSync(join(root, '.git'))) return; if (!root || !existsSync(join(root, '.git'))) return;
const branch = currentBranch(root); const branch = currentBranch(root);
const auto = autoUpdateEnabled();
// Auto-update: on a throttled tick, fire `agenthub update` directly in the
// background. It does its OWN fresh fetch + reset + build and no-ops when
// already current — so it self-heals on the FIRST invocation. (The old
// path checked a possibly-stale origin ref, so it lagged a fetch cycle and
// took two invocations to ever fire.)
if (autoUpdateEnabled()) {
const stamp = stampPath('last-auto-update');
if (Date.now() - readStamp(stamp) > AUTO_CHECK_INTERVAL_MS) {
writeStamp(stamp);
process.stderr.write('\n🔄 AgentHub: checking for updates in the background…\n\n');
spawnBackgroundUpdate(root);
}
return;
}
// Notify-only: throttled background fetch keeps the behind-count fresh,
// then a one-line hint if the install is behind.
const stamp = stampPath('last-update-check'); const stamp = stampPath('last-update-check');
const interval = auto ? AUTO_CHECK_INTERVAL_MS : CHECK_INTERVAL_MS; if (Date.now() - readStamp(stamp) > CHECK_INTERVAL_MS) {
if (Date.now() - readStamp(stamp) > interval) {
writeStamp(stamp); writeStamp(stamp);
try { try {
spawn('git', ['fetch', '--quiet', 'origin', branch], { cwd: root, detached: true, stdio: 'ignore' }).unref(); spawn('git', ['fetch', '--quiet', 'origin', branch], { cwd: root, detached: true, stdio: 'ignore' }).unref();
@ -199,18 +213,7 @@ export function maybeNotifyUpdate(rootOverride?: string): void {
} catch { } catch {
return; return;
} }
if (behind === '0') return; if (behind !== '0') {
if (auto) {
// Only one background update per cooldown window — a second command while
// the reset+build is in flight must not spawn a competing update.
const autoStamp = stampPath('last-auto-update');
if (Date.now() - readStamp(autoStamp) > AUTO_UPDATE_COOLDOWN_MS) {
writeStamp(autoStamp);
process.stderr.write(`\n🔄 AgentHub: updating in background (${behind} commit(s) behind)…\n\n`);
spawnBackgroundUpdate(root);
}
} else {
process.stderr.write(`\n🔄 A newer AgentHub is available (${behind} commit(s) behind). Run \`agenthub update\`.\n\n`); process.stderr.write(`\n🔄 A newer AgentHub is available (${behind} commit(s) behind). Run \`agenthub update\`.\n\n`);
} }
} catch { } catch {

View File

@ -114,7 +114,7 @@ async function runRemote(serverUrl: string, fn: () => Promise<void>): Promise<vo
export function createProgram(cwd: string): Command { export function createProgram(cwd: string): Command {
const program = new Command('agenthub') const program = new Command('agenthub')
.description('Local coordination layer for AI coding agents') .description('Local coordination layer for AI coding agents')
.version('0.6.0') .version('0.6.1')
.option('--server <url>', 'AgentHub server URL (env: AGENTHUB_SERVER)'); .option('--server <url>', 'AgentHub server URL (env: AGENTHUB_SERVER)');
program program