@evident-ai/cli 3.4.1-dev.c03b461 → 3.4.1-dev.cb56b3f

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -119,6 +119,9 @@ Options:
119
119
  - `--opencode-config-overlay <path>` — Apply a runner-provided OpenCode config
120
120
  before starting OpenCode. Relative paths are resolved from the working directory;
121
121
  an existing `opencode.jsonc` is replaced before `opencode.json`.
122
+ - `--credential-sync-marker <path>` — Own the interval credential sync and write
123
+ this marker once the shutdown flush has finished, so the runner image's lifecycle
124
+ hooks can wait on it.
122
125
  - `--claude-usage-reporting <mode>` — Whether to report the local Claude Code
123
126
  subscription's rate-limit usage to Evident, so it shows on the runner page:
124
127
  `auto` (default) reports it when a usable Claude Code login is found on this
package/dist/index.js CHANGED
@@ -1183,7 +1183,7 @@ async function claudeUsage() {
1183
1183
  }
1184
1184
 
1185
1185
  // src/commands/run.ts
1186
- import { chmodSync as chmodSync3, existsSync as existsSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
1186
+ import { chmodSync as chmodSync3, existsSync as existsSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "fs";
1187
1187
  import { homedir as homedir5 } from "os";
1188
1188
  import { isAbsolute as isAbsolute3, join as join9, parse, resolve as resolvePath2 } from "path";
1189
1189
  import chalk6 from "chalk";
@@ -1728,10 +1728,14 @@ function runSynchroniser(args, opts) {
1728
1728
  let stderr = "";
1729
1729
  let settled = false;
1730
1730
  const timer = {};
1731
+ let abortListener;
1732
+ let spawnListener;
1731
1733
  const finish = (result) => {
1732
1734
  if (settled) return;
1733
1735
  settled = true;
1734
1736
  if (timer.handle) clearTimeout(timer.handle);
1737
+ if (abortListener) opts.signal?.removeEventListener("abort", abortListener);
1738
+ if (spawnListener) child.removeListener("spawn", spawnListener);
1735
1739
  resolve4(result);
1736
1740
  };
1737
1741
  try {
@@ -1757,6 +1761,25 @@ function runSynchroniser(args, opts) {
1757
1761
  child.once("close", (code) => {
1758
1762
  finish({ code, stdout, stderr, timedOut: false });
1759
1763
  });
1764
+ if (opts.signal) {
1765
+ const killChild = () => {
1766
+ if (child.pid === void 0) {
1767
+ if (!spawnListener) {
1768
+ spawnListener = killChild;
1769
+ child.once("spawn", spawnListener);
1770
+ }
1771
+ return;
1772
+ }
1773
+ child.kill("SIGKILL");
1774
+ };
1775
+ abortListener = killChild;
1776
+ if (opts.signal.aborted) {
1777
+ abortListener();
1778
+ } else {
1779
+ opts.signal.addEventListener("abort", abortListener, { once: true });
1780
+ if (opts.signal.aborted) abortListener();
1781
+ }
1782
+ }
1760
1783
  timer.handle = setTimeout(
1761
1784
  () => {
1762
1785
  child.kill("SIGKILL");
@@ -3844,8 +3867,8 @@ function connectTunnel(options) {
3844
3867
  try {
3845
3868
  message = JSON.parse(data.toString());
3846
3869
  } catch (error2) {
3847
- const errorMessage = error2 instanceof Error ? error2.message : "Unknown error";
3848
- onError?.(`Failed to handle message: ${errorMessage}`);
3870
+ const errorMessage2 = error2 instanceof Error ? error2.message : "Unknown error";
3871
+ onError?.(`Failed to handle message: ${errorMessage2}`);
3849
3872
  return;
3850
3873
  }
3851
3874
  if (isStreamFrame(message)) {
@@ -5619,7 +5642,7 @@ var ChannelDriver = class _ChannelDriver {
5619
5642
  this.signalDispatchNotStarted(conv, message, "session_existence_unknown");
5620
5643
  break;
5621
5644
  }
5622
- const errorMessage = err instanceof Error ? err.message : String(err);
5645
+ const errorMessage2 = err instanceof Error ? err.message : String(err);
5623
5646
  this.sessions.delete(conv.id);
5624
5647
  this.supersede(conv.id, sessionId);
5625
5648
  this.log({
@@ -5628,7 +5651,7 @@ var ChannelDriver = class _ChannelDriver {
5628
5651
  conversation_id: conv.id,
5629
5652
  message_id: message.id
5630
5653
  });
5631
- await this.markFailed(conv.id, message.id, null, errorMessage).catch((markErr) => {
5654
+ await this.markFailed(conv.id, message.id, null, errorMessage2).catch((markErr) => {
5632
5655
  this.log({
5633
5656
  level: "warn",
5634
5657
  message: `markFailed PATCH for message ${message.id.slice(0, 8)} (conversation ${conv.id.slice(0, 8)}) failed (best-effort, not retried): ${markErr instanceof Error ? markErr.message : String(markErr)}`,
@@ -5639,7 +5662,7 @@ var ChannelDriver = class _ChannelDriver {
5639
5662
  });
5640
5663
  this.log({
5641
5664
  level: "error",
5642
- message: `Message ${message.id.slice(0, 8)} dispatch failed: ${errorMessage}`,
5665
+ message: `Message ${message.id.slice(0, 8)} dispatch failed: ${errorMessage2}`,
5643
5666
  conversation_id: conv.id,
5644
5667
  message_id: message.id
5645
5668
  });
@@ -5660,14 +5683,14 @@ var ChannelDriver = class _ChannelDriver {
5660
5683
  this.unconfirmedDispatchFailures.delete(message.id);
5661
5684
  this.sessions.delete(conv.id);
5662
5685
  this.supersede(conv.id, sessionId);
5663
- const errorMessage = `OpenCode accepted this message ${streak} times in a row but never confirmed its assigned id (session ${sessionId.slice(0, 8)}'s message list could not be read back) \u2014 the session was abandoned; a fresh one is used for further messages.`;
5686
+ const errorMessage2 = `OpenCode accepted this message ${streak} times in a row but never confirmed its assigned id (session ${sessionId.slice(0, 8)}'s message list could not be read back) \u2014 the session was abandoned; a fresh one is used for further messages.`;
5664
5687
  this.log({
5665
5688
  level: "error",
5666
- message: errorMessage,
5689
+ message: errorMessage2,
5667
5690
  conversation_id: conv.id,
5668
5691
  message_id: message.id
5669
5692
  });
5670
- await this.markFailed(conv.id, message.id, null, errorMessage).catch((markErr) => {
5693
+ await this.markFailed(conv.id, message.id, null, errorMessage2).catch((markErr) => {
5671
5694
  this.log({
5672
5695
  level: "warn",
5673
5696
  message: `markFailed PATCH for message ${message.id.slice(0, 8)} (conversation ${conv.id.slice(0, 8)}) failed (best-effort, not retried): ${markErr instanceof Error ? markErr.message : String(markErr)}`,
@@ -7631,14 +7654,14 @@ var ChannelDriver = class _ChannelDriver {
7631
7654
  this.unconfirmedDispatchFailures.delete(row.id);
7632
7655
  this.sessions.delete(readoptConv.id);
7633
7656
  this.supersede(readoptConv.id, sessionId);
7634
- const errorMessage = `OpenCode accepted this re-dispatched message ${streak} times in a row but never confirmed its assigned id (session ${sessionId.slice(0, 8)}'s message list could not be read back) \u2014 the session was abandoned; a fresh one is used for further messages.`;
7657
+ const errorMessage2 = `OpenCode accepted this re-dispatched message ${streak} times in a row but never confirmed its assigned id (session ${sessionId.slice(0, 8)}'s message list could not be read back) \u2014 the session was abandoned; a fresh one is used for further messages.`;
7635
7658
  this.log({
7636
7659
  level: "error",
7637
- message: errorMessage,
7660
+ message: errorMessage2,
7638
7661
  conversation_id: row.conversation_id,
7639
7662
  message_id: row.id
7640
7663
  });
7641
- await this.markFailed(row.conversation_id, row.id, null, errorMessage).catch((markErr) => {
7664
+ await this.markFailed(row.conversation_id, row.id, null, errorMessage2).catch((markErr) => {
7642
7665
  this.log({
7643
7666
  level: "warn",
7644
7667
  message: `markFailed PATCH for message ${row.id.slice(0, 8)} (conversation ${row.conversation_id.slice(0, 8)}) failed (best-effort, not retried): ${markErr instanceof Error ? markErr.message : String(markErr)}`,
@@ -9079,6 +9102,241 @@ function applyRunnerOpenCodeConfig({
9079
9102
  log3(`Applied runner OpenCode config ${source} to ${join8(cwd, target)}`);
9080
9103
  }
9081
9104
 
9105
+ // src/lib/credential-sync.ts
9106
+ import { renameSync, writeFileSync as writeFileSync5 } from "fs";
9107
+ var CREDENTIAL_SYNC_TICK_TIMEOUT_MS = 3e4;
9108
+ var CREDENTIAL_FLUSH_DEADLINE_MS = 8e3;
9109
+ var CREDENTIAL_FLUSH_ABORT_GRACE_MS = 500;
9110
+ var DEFAULT_CREDENTIAL_SYNC_INTERVAL_SECONDS = 60;
9111
+ var STORES = ["claude", "opencode"];
9112
+ var MAX_FLUSH_PASSES = 2;
9113
+ function outcomesWith(outcome) {
9114
+ return { claude: outcome, opencode: outcome };
9115
+ }
9116
+ function errorMessage(error2) {
9117
+ return error2 instanceof Error ? error2.message : String(error2);
9118
+ }
9119
+ function waitForSettlement(promise, timeoutMs) {
9120
+ return new Promise((resolve4) => {
9121
+ let settled = false;
9122
+ const timer = setTimeout(() => finish(false), Math.max(0, timeoutMs));
9123
+ const finish = (value) => {
9124
+ if (settled) return;
9125
+ settled = true;
9126
+ clearTimeout(timer);
9127
+ resolve4(value);
9128
+ };
9129
+ promise.then(
9130
+ () => finish(true),
9131
+ () => finish(true)
9132
+ );
9133
+ });
9134
+ }
9135
+ function writeMarker(markerPath, outcomes, log3) {
9136
+ const body = `${STORES.map((store) => `${store}=${outcomes[store]}`).join("\n")}
9137
+ `;
9138
+ const temporaryPath = `${markerPath}.tmp`;
9139
+ try {
9140
+ writeFileSync5(temporaryPath, body, { mode: 384 });
9141
+ renameSync(temporaryPath, markerPath);
9142
+ } catch (error2) {
9143
+ log3(`CREDENTIAL-FLUSH-MARKER-UNWRITABLE: ${markerPath}: ${errorMessage(error2)}`, "warn");
9144
+ }
9145
+ }
9146
+ function intervalSeconds(env, log3) {
9147
+ const raw = env.CREDS_SYNC_INTERVAL;
9148
+ if (raw === void 0 || /^[1-9][0-9]*$/.test(raw) && Number.isSafeInteger(Number(raw))) {
9149
+ return raw === void 0 ? DEFAULT_CREDENTIAL_SYNC_INTERVAL_SECONDS : Number(raw);
9150
+ }
9151
+ log3(
9152
+ `CREDENTIAL-SYNC-INTERVAL-INVALID: CREDS_SYNC_INTERVAL='${raw}' is not a positive integer; using ${DEFAULT_CREDENTIAL_SYNC_INTERVAL_SECONDS}s`,
9153
+ "warn"
9154
+ );
9155
+ return DEFAULT_CREDENTIAL_SYNC_INTERVAL_SECONDS;
9156
+ }
9157
+ async function runFlushStore(store, env, synchroniserRunner, log3, deadlineAt) {
9158
+ const remainingMs = deadlineAt - Date.now();
9159
+ if (remainingMs <= 0) return { outcome: "timeout", orphaned: false };
9160
+ const controller = new AbortController();
9161
+ let result;
9162
+ let failed = false;
9163
+ const completion = Promise.resolve().then(
9164
+ () => synchroniserRunner(["sync-once", store], {
9165
+ timeoutMs: remainingMs,
9166
+ env,
9167
+ signal: controller.signal
9168
+ })
9169
+ ).then(
9170
+ (value) => {
9171
+ result = value;
9172
+ },
9173
+ (error2) => {
9174
+ failed = true;
9175
+ log3(`CREDENTIAL-FLUSH-ERROR: ${store}: ${errorMessage(error2)}`, "warn");
9176
+ }
9177
+ );
9178
+ const abortTimer = setTimeout(() => controller.abort(), remainingMs);
9179
+ const settledBeforeDeadline = await waitForSettlement(completion, remainingMs);
9180
+ clearTimeout(abortTimer);
9181
+ if (!settledBeforeDeadline) {
9182
+ controller.abort();
9183
+ const settledAfterAbort = await waitForSettlement(completion, CREDENTIAL_FLUSH_ABORT_GRACE_MS);
9184
+ if (!settledAfterAbort) return { outcome: "timeout", orphaned: true };
9185
+ return { outcome: "timeout", orphaned: false };
9186
+ }
9187
+ if (failed || !result) return { outcome: "failed", orphaned: false };
9188
+ if (result.timedOut || Date.now() >= deadlineAt) {
9189
+ return { outcome: "timeout", orphaned: false };
9190
+ }
9191
+ return { outcome: result.code === 0 ? "ok" : "failed", orphaned: false };
9192
+ }
9193
+ function createCredentialSync({
9194
+ markerPath,
9195
+ env,
9196
+ log: log3,
9197
+ synchroniserRunner = runSynchroniser
9198
+ }) {
9199
+ const persistenceDisabled = !env.PERSISTENCE_BUCKET;
9200
+ let disabled = persistenceDisabled;
9201
+ let armed = false;
9202
+ let stopped = false;
9203
+ let timer;
9204
+ let inFlight;
9205
+ let activeTickAbort;
9206
+ let lastTickFailed;
9207
+ let flushPromise;
9208
+ const scheduleTick = (intervalMs, startTick2) => {
9209
+ if (stopped) return;
9210
+ timer = setTimeout(() => {
9211
+ timer = void 0;
9212
+ startTick2();
9213
+ }, intervalMs);
9214
+ };
9215
+ const startTick = (intervalMs) => {
9216
+ if (stopped) return;
9217
+ const controller = new AbortController();
9218
+ activeTickAbort = controller;
9219
+ const tick = (async () => {
9220
+ const outcomes = {
9221
+ claude: "failed",
9222
+ opencode: "failed"
9223
+ };
9224
+ for (const store of STORES) {
9225
+ if (controller.signal.aborted) break;
9226
+ try {
9227
+ const result = await synchroniserRunner(["sync-once", store], {
9228
+ timeoutMs: CREDENTIAL_SYNC_TICK_TIMEOUT_MS,
9229
+ env,
9230
+ signal: controller.signal
9231
+ });
9232
+ outcomes[store] = !result.timedOut && result.code === 0 ? "ok" : "failed";
9233
+ } catch (error2) {
9234
+ outcomes[store] = "failed";
9235
+ log3(`CREDENTIAL-SYNC-TICK-ERROR: ${store}: ${errorMessage(error2)}`, "debug");
9236
+ }
9237
+ }
9238
+ const failed = STORES.some((store) => outcomes[store] === "failed");
9239
+ log3(
9240
+ `CREDENTIAL-SYNC-TICK: claude=${outcomes.claude}, opencode=${outcomes.opencode}`,
9241
+ "debug"
9242
+ );
9243
+ if (failed && lastTickFailed !== true) {
9244
+ log3(
9245
+ "CREDENTIAL-SYNC-FAILED: an interval credential sync failed; retrying on the next tick",
9246
+ "warn"
9247
+ );
9248
+ } else if (!failed && lastTickFailed === true) {
9249
+ log3("CREDENTIAL-SYNC-RECOVERED: interval credential sync succeeded again", "warn");
9250
+ }
9251
+ lastTickFailed = failed;
9252
+ })().finally(() => {
9253
+ if (activeTickAbort === controller) activeTickAbort = void 0;
9254
+ if (inFlight === tick) inFlight = void 0;
9255
+ scheduleTick(intervalMs, () => startTick(intervalMs));
9256
+ });
9257
+ inFlight = tick;
9258
+ };
9259
+ const performFlush = async () => {
9260
+ stopped = true;
9261
+ if (timer) {
9262
+ clearTimeout(timer);
9263
+ timer = void 0;
9264
+ }
9265
+ const deadlineAt = Date.now() + CREDENTIAL_FLUSH_DEADLINE_MS;
9266
+ if (inFlight) {
9267
+ const settled = await waitForSettlement(inFlight, CREDENTIAL_FLUSH_DEADLINE_MS);
9268
+ if (!settled) {
9269
+ activeTickAbort?.abort();
9270
+ const settledAfterAbort = await waitForSettlement(
9271
+ inFlight,
9272
+ CREDENTIAL_FLUSH_ABORT_GRACE_MS
9273
+ );
9274
+ if (!settledAfterAbort) {
9275
+ log3(
9276
+ "CREDENTIAL-FLUSH-ORPHANED-TICK: a sync-once child could not be proven settled; skipping the boundary flush rather than becoming a second writer",
9277
+ "warn"
9278
+ );
9279
+ return { outcomes: outcomesWith("timeout"), orphaned: true };
9280
+ }
9281
+ }
9282
+ }
9283
+ if (disabled) return { outcomes: outcomesWith("skipped"), orphaned: false };
9284
+ const outcomes = outcomesWith("timeout");
9285
+ for (const store of STORES) {
9286
+ const result = await runFlushStore(store, env, synchroniserRunner, log3, deadlineAt);
9287
+ if (result.orphaned) {
9288
+ log3(
9289
+ "CREDENTIAL-FLUSH-ORPHANED-CHILD: a flush sync-once child could not be proven settled; refusing another flush pass rather than becoming a second writer",
9290
+ "warn"
9291
+ );
9292
+ return { outcomes: outcomesWith("timeout"), orphaned: true };
9293
+ }
9294
+ outcomes[store] = result.outcome;
9295
+ }
9296
+ return { outcomes, orphaned: false };
9297
+ };
9298
+ let flushPasses = 0;
9299
+ let lastFlush;
9300
+ return {
9301
+ arm() {
9302
+ if (stopped || armed) return;
9303
+ armed = true;
9304
+ if (persistenceDisabled) {
9305
+ disabled = true;
9306
+ log3(
9307
+ "CREDENTIAL-SYNC-DISABLED: LITESTREAM_BUCKET/LITESTREAM_PREFIX are not both set; no interval credential sync this boot",
9308
+ "warn"
9309
+ );
9310
+ return;
9311
+ }
9312
+ disabled = false;
9313
+ const intervalMs = intervalSeconds(env, log3) * 1e3;
9314
+ scheduleTick(intervalMs, () => startTick(intervalMs));
9315
+ },
9316
+ async stopAndFlush(publish) {
9317
+ let result;
9318
+ const runningFlush = flushPromise;
9319
+ if (runningFlush) {
9320
+ result = await runningFlush;
9321
+ } else if (flushPasses >= MAX_FLUSH_PASSES || lastFlush?.orphaned) {
9322
+ result = lastFlush ?? { outcomes: outcomesWith("timeout"), orphaned: true };
9323
+ } else {
9324
+ flushPasses++;
9325
+ const currentFlush = performFlush();
9326
+ flushPromise = currentFlush;
9327
+ try {
9328
+ result = await currentFlush;
9329
+ lastFlush = result;
9330
+ } finally {
9331
+ if (flushPromise === currentFlush) flushPromise = void 0;
9332
+ }
9333
+ }
9334
+ if (publish) writeMarker(markerPath, result.outcomes, log3);
9335
+ return result.outcomes;
9336
+ }
9337
+ };
9338
+ }
9339
+
9082
9340
  // src/commands/run.ts
9083
9341
  var MAX_ACTIVITY_LOG_ENTRIES = 10;
9084
9342
  var CHANNEL_POLL_INTERVAL_MS = Number(process.env.EVIDENT_CHANNEL_POLL_INTERVAL_MS) || 2e3;
@@ -9418,8 +9676,8 @@ async function driveChannels(state, driver) {
9418
9676
  state.running = false;
9419
9677
  break;
9420
9678
  }
9421
- const errorMessage = error2 instanceof Error ? error2.message : String(error2);
9422
- logActivity(state, { type: "error", error: `Channel processing error: ${errorMessage}` });
9679
+ const errorMessage2 = error2 instanceof Error ? error2.message : String(error2);
9680
+ logActivity(state, { type: "error", error: `Channel processing error: ${errorMessage2}` });
9423
9681
  if (state.interactive) displayStatus(state);
9424
9682
  if (driver.hasInFlightWatchers()) {
9425
9683
  consecutiveDrainFailures = 0;
@@ -9889,21 +10147,39 @@ async function cleanup(state, opts = {}) {
9889
10147
  clearTimeout(state.resourceUsageTimer);
9890
10148
  state.resourceUsageTimer = null;
9891
10149
  }
10150
+ const credentialSync = state.credentialSync;
10151
+ const flushCredentials = credentialSync ? async (phase, publish) => {
10152
+ await timeShutdownPhase(state, durations, phase, async () => {
10153
+ const outcomes = await credentialSync.stopAndFlush(publish);
10154
+ const level = Object.values(outcomes).every((outcome) => outcome === "ok") ? "info" : "warn";
10155
+ log2(
10156
+ state,
10157
+ `Credential flush: claude=${outcomes.claude}, opencode=${outcomes.opencode}`,
10158
+ level
10159
+ );
10160
+ });
10161
+ } : void 0;
10162
+ let drainSettled = true;
9892
10163
  if (opts.graceful && state.channelDriver) {
9893
10164
  state.channelDriver.stop();
10165
+ }
10166
+ if (flushCredentials) {
10167
+ await flushCredentials("credential_flush", !(opts.graceful && state.channelDriver));
10168
+ }
10169
+ if (opts.graceful && state.channelDriver) {
9894
10170
  log2(state, "Draining in-flight channel work before shutdown...");
9895
10171
  if (state.interactive) {
9896
10172
  logActivity(state, { type: "info", message: "Draining in-flight work before shutdown..." });
9897
10173
  displayStatus(state);
9898
10174
  }
9899
10175
  const driver = state.channelDriver;
9900
- const settled = await timeShutdownPhase(
10176
+ drainSettled = await timeShutdownPhase(
9901
10177
  state,
9902
10178
  durations,
9903
10179
  "drain",
9904
10180
  () => driver.waitForInFlight(SHUTDOWN_DRAIN_TIMEOUT_MS)
9905
10181
  );
9906
- if (!settled) {
10182
+ if (!drainSettled) {
9907
10183
  logActivity(state, {
9908
10184
  type: "info",
9909
10185
  message: "Shutdown drain timed out with work still in flight \u2014 leaving it for restart recovery"
@@ -9911,6 +10187,9 @@ async function cleanup(state, opts = {}) {
9911
10187
  if (state.interactive) displayStatus(state);
9912
10188
  }
9913
10189
  }
10190
+ if (opts.graceful && state.channelDriver && flushCredentials && drainSettled) {
10191
+ await flushCredentials("credential_flush_final", true);
10192
+ }
9914
10193
  await timeShutdownPhase(state, durations, "offline_notify", () => notifyOffline(state));
9915
10194
  if (state.connection) {
9916
10195
  const connection = state.connection;
@@ -9997,9 +10276,23 @@ async function run(options) {
9997
10276
  openaiUsageTimer: null,
9998
10277
  openaiUsageRearm: null,
9999
10278
  resourceUsageTimer: null,
10279
+ credentialSync: null,
10000
10280
  authHeader: ""
10001
10281
  };
10002
10282
  setTelemetryAuthProvider(() => ({ authHeader: state.authHeader, agentId: state.agentId }));
10283
+ if (options.credentialSyncMarker) {
10284
+ state.credentialSync = createCredentialSync({
10285
+ markerPath: options.credentialSyncMarker,
10286
+ env: process.env,
10287
+ log: (message, level = "info") => {
10288
+ if (level === "error") {
10289
+ logActivity(state, { type: "error", error: message });
10290
+ } else {
10291
+ logActivity(state, { type: "info", level, message });
10292
+ }
10293
+ }
10294
+ });
10295
+ }
10003
10296
  if (fileSyncDirectories.length > 0) {
10004
10297
  log2(state, `File sync enabled for: ${fileSyncDirectories.join(", ")}`);
10005
10298
  } else {
@@ -10207,6 +10500,7 @@ async function run(options) {
10207
10500
  await restoreCredentialStores(credentialContext);
10208
10501
  if (githubTokenPopulated) await configureGitHubAccess(credentialContext);
10209
10502
  }
10503
+ state.credentialSync?.arm();
10210
10504
  let sessionDbVerifyFatal = false;
10211
10505
  if (!options.restoreSessionDb) {
10212
10506
  log2(state, "Skipping session-DB restore: --restore-session-db was not passed", "debug");
@@ -10276,7 +10570,7 @@ async function run(options) {
10276
10570
  state.opencodeVersion = oc.version;
10277
10571
  if (options.opencodePidFile && oc.process?.pid !== void 0) {
10278
10572
  try {
10279
- writeFileSync5(options.opencodePidFile, `${oc.process.pid}
10573
+ writeFileSync6(options.opencodePidFile, `${oc.process.pid}
10280
10574
  `, { mode: 384 });
10281
10575
  chmodSync3(options.opencodePidFile, 384);
10282
10576
  } catch (error2) {
@@ -10392,7 +10686,7 @@ async function run(options) {
10392
10686
  });
10393
10687
  try {
10394
10688
  if (litestreamProcess.pid !== void 0) {
10395
- writeFileSync5(options.litestreamPidFile, `${litestreamProcess.pid}
10689
+ writeFileSync6(options.litestreamPidFile, `${litestreamProcess.pid}
10396
10690
  `, {
10397
10691
  mode: 384
10398
10692
  });
@@ -10722,6 +11016,9 @@ program.command("run").description("Connect to Evident and process messages").op
10722
11016
  ).option(
10723
11017
  "--opencode-config-overlay <path>",
10724
11018
  "Apply this runner-provided OpenCode config before starting OpenCode."
11019
+ ).option(
11020
+ "--credential-sync-marker <path>",
11021
+ "Own the interval credential sync and write this marker once the shutdown flush has finished, so the runner image's lifecycle hooks can wait on it."
10725
11022
  ).action(
10726
11023
  (options) => {
10727
11024
  run({
@@ -10760,7 +11057,8 @@ program.command("run").description("Connect to Evident and process messages").op
10760
11057
  sessionDbNoReplicateMarker: options.sessionDbNoReplicateMarker,
10761
11058
  restoreSessionDb: options.restoreSessionDb,
10762
11059
  restoreRunnerCredentials: options.restoreRunnerCredentials,
10763
- opencodeConfigOverlay: options.opencodeConfigOverlay
11060
+ opencodeConfigOverlay: options.opencodeConfigOverlay,
11061
+ credentialSyncMarker: options.credentialSyncMarker
10764
11062
  });
10765
11063
  }
10766
11064
  );