automata-cli 0.12.0-develop.533 → 0.12.0-develop.562

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.
Files changed (2) hide show
  1. package/dist/index.js +412 -109
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2633,7 +2633,7 @@ function addLabel(target, name, description) {
2633
2633
  const edit = target.kind === "pr" ? "pr" : "issue";
2634
2634
  const args = [edit, "edit", String(target.number), "--add-label", name];
2635
2635
  let result = run4("gh", args);
2636
- if (result.status !== 0 && isMissingLabelError(result.stderr)) {
2636
+ if (result.status !== 0 && isMissingLabelError(result.stderr, name)) {
2637
2637
  const created = run4("gh", ["label", "create", name, "--description", description]);
2638
2638
  if (created.status !== 0 && !/already exists/i.test(created.stderr)) {
2639
2639
  throw new Error(created.stderr.trim() || `Failed to create the label ${name}.`);
@@ -2732,8 +2732,11 @@ var MISSING_LABEL_PATTERNS = [
2732
2732
  /could not resolve to a label/i,
2733
2733
  /\blabels?\b[^\n]*\b(?:not found|does not exist)\b/i
2734
2734
  ];
2735
- function isMissingLabelError(stderr) {
2736
- return MISSING_LABEL_PATTERNS.some((pattern) => pattern.test(stderr));
2735
+ function isMissingLabelError(stderr, name) {
2736
+ if (MISSING_LABEL_PATTERNS.some((pattern) => pattern.test(stderr))) {
2737
+ return true;
2738
+ }
2739
+ return name !== void 0 && stderr.includes(`'${name}' not found`);
2737
2740
  }
2738
2741
  function createDraftPullRequest(input) {
2739
2742
  const base = [
@@ -2752,7 +2755,7 @@ function createDraftPullRequest(input) {
2752
2755
  if (input.label !== void 0 && input.label.length > 0) {
2753
2756
  const labelled = run4("gh", [...base, "--label", input.label]);
2754
2757
  if (labelled.status === 0) return parseCreatedPrUrl(labelled.stdout, input.head);
2755
- if (!isMissingLabelError(labelled.stderr)) {
2758
+ if (!isMissingLabelError(labelled.stderr, input.label)) {
2756
2759
  throw new Error(
2757
2760
  labelled.stderr.trim() || `Failed to open a draft pull request for ${input.head}.`
2758
2761
  );
@@ -2966,7 +2969,7 @@ function runClaude(prompt, options = {}) {
2966
2969
  if (options.printSteps !== true) return;
2967
2970
  try {
2968
2971
  const event = JSON.parse(line);
2969
- formatEvent(event, turnCount);
2972
+ formatEvent(event, turnCount, options.echoResult !== false);
2970
2973
  if (event["type"] === "assistant") turnCount++;
2971
2974
  } catch {
2972
2975
  }
@@ -3056,7 +3059,7 @@ function formatContentBlock(block) {
3056
3059
  }
3057
3060
  return null;
3058
3061
  }
3059
- function formatResultEvent(event) {
3062
+ function formatResultEvent(event, echoResult) {
3060
3063
  const result = event["result"];
3061
3064
  const cost = event["cost_usd"];
3062
3065
  const duration = event["duration_ms"];
@@ -3070,16 +3073,16 @@ function formatResultEvent(event) {
3070
3073
  process.stderr.write(` [info] ${parts.join(" | ")}
3071
3074
  `);
3072
3075
  }
3073
- if (result) {
3076
+ if (result && echoResult) {
3074
3077
  process.stdout.write(result + "\n");
3075
3078
  }
3076
3079
  }
3077
- function formatEvent(event, turnCount) {
3080
+ function formatEvent(event, turnCount, echoResult = true) {
3078
3081
  const type = event["type"];
3079
3082
  if (type === "assistant") {
3080
3083
  formatAssistantEvent(event, turnCount);
3081
3084
  } else if (type === "result") {
3082
- formatResultEvent(event);
3085
+ formatResultEvent(event, echoResult);
3083
3086
  }
3084
3087
  }
3085
3088
  function asText(value, fallback) {
@@ -5432,6 +5435,44 @@ async function scrubExcerpt(execution, excerpt, timeoutMs = SECOND_OPINION_TIMEO
5432
5435
  return result.ok ? interpretScrubOutput(result.stdout, excerpt) : result;
5433
5436
  }
5434
5437
 
5438
+ // src/run/claudeAuth.ts
5439
+ var CLAUDE_AUTH_PATTERNS = [
5440
+ /Failed to authenticate/i,
5441
+ /OAuth (?:session|token) (?:has )?expired/i,
5442
+ /Invalid API key\s*[·\-:]\s*Please run \/login/i,
5443
+ /Please run \/login/i
5444
+ ];
5445
+ var CLAUDE_AUTH_MARKER = "<!-- automata:claude-auth -->";
5446
+ function detectClaudeAuthFailure(text) {
5447
+ return CLAUDE_AUTH_PATTERNS.some((pattern) => pattern.test(text));
5448
+ }
5449
+ var CLAUDE_AUTH_TAIL_MAX = 8192;
5450
+ var ClaudeAuthTail = class {
5451
+ text = "";
5452
+ output(_stream, chunk) {
5453
+ this.text = (this.text + chunk).slice(-CLAUDE_AUTH_TAIL_MAX);
5454
+ }
5455
+ exited() {
5456
+ }
5457
+ get tail() {
5458
+ return this.text;
5459
+ }
5460
+ };
5461
+ var CLAUDE_AUTH_REASON = "Claude Code is not signed in (its login expired and could not be refreshed)";
5462
+ var CLAUDE_AUTH_REMEDY = "Run `claude /login` again on the machine that runs automata (inside the devcontainer, if it runs there), then reply to have another attempt made.";
5463
+ function renderClaudeAuthComment(surface, excerpt) {
5464
+ const lines = [
5465
+ `automata do-work: the agent run stopped before it posted an answer on ${surface}. ${CLAUDE_AUTH_REASON}.`,
5466
+ CLAUDE_AUTH_REMEDY,
5467
+ "This comment is posted once. Automata does not post it again while the problem remains."
5468
+ ];
5469
+ if (excerpt.trim() !== "") {
5470
+ lines.push("", "Last lines of the agent's output:", "```text", excerpt.replaceAll("```", "'''"), "```");
5471
+ }
5472
+ return `${lines.join("\n")}
5473
+ ${CLAUDE_AUTH_MARKER}`;
5474
+ }
5475
+
5435
5476
  // src/run/answerRecovery.ts
5436
5477
  import { closeSync, fstatSync, openSync, readSync } from "fs";
5437
5478
  var RECOVERY_TIMEOUT_MS = 3e5;
@@ -6256,6 +6297,14 @@ function describeTickHistory(ticks, cadence, now, logPath, lines, problems) {
6256
6297
  )
6257
6298
  );
6258
6299
  }
6300
+ if (ticks[0].note === "claude-auth") {
6301
+ problems.push(
6302
+ finding(
6303
+ `${CLAUDE_AUTH_REASON}, so the last tick stopped before it answered; ${CLAUDE_AUTH_REMEDY}`,
6304
+ "claude /login"
6305
+ )
6306
+ );
6307
+ }
6259
6308
  const lockHeld = ticks.filter((tick) => tick.note === "lock-held").length;
6260
6309
  const withRuns = ticks.filter((tick) => tick.runs > 0).length;
6261
6310
  lines.push(
@@ -6635,14 +6684,20 @@ function requireParticipants(config) {
6635
6684
  );
6636
6685
  }
6637
6686
  if (!config.issueDiscoveryTechnique) {
6638
- failSettings("No issue discovery technique configured. Run `automata config set issue-discovery-technique <value>`.");
6687
+ failSettings(
6688
+ "No issue discovery technique configured. Run `automata config set issue-discovery-technique <value>`."
6689
+ );
6639
6690
  }
6640
6691
  if (!config.issueDiscoveryValue) {
6641
- failSettings("No issue discovery value configured. Run `automata config set issue-discovery-value <value>`.");
6692
+ failSettings(
6693
+ "No issue discovery value configured. Run `automata config set issue-discovery-value <value>`."
6694
+ );
6642
6695
  }
6643
6696
  const allowedUsers = (config.allowedUsers ?? []).filter((user) => user.trim().length > 0);
6644
6697
  if (allowedUsers.length === 0) {
6645
- failSettings("No allowed users configured. Run `automata config set allowed-users <user1,user2>`.");
6698
+ failSettings(
6699
+ "No allowed users configured. Run `automata config set allowed-users <user1,user2>`."
6700
+ );
6646
6701
  }
6647
6702
  const agentUser = (config.agentUser ?? "").trim();
6648
6703
  if (agentUser.length === 0) {
@@ -6861,8 +6916,20 @@ function validateDoWorkConfig(section2) {
6861
6916
  failSettings(`doWork.executor must be 'claude' or 'codex', got ${JSON.stringify(executor)}.`);
6862
6917
  }
6863
6918
  validateOptionalString(section2, "baseBranch", "doWork.baseBranch");
6864
- validateOptionalInt(section2, "maxRunsPerTick", "doWork.maxRunsPerTick", 0, "a non-negative integer (0 = unlimited)");
6865
- validateOptionalInt(section2, "lockStaleMinutes", "doWork.lockStaleMinutes", 1, "a positive integer");
6919
+ validateOptionalInt(
6920
+ section2,
6921
+ "maxRunsPerTick",
6922
+ "doWork.maxRunsPerTick",
6923
+ 0,
6924
+ "a non-negative integer (0 = unlimited)"
6925
+ );
6926
+ validateOptionalInt(
6927
+ section2,
6928
+ "lockStaleMinutes",
6929
+ "doWork.lockStaleMinutes",
6930
+ 1,
6931
+ "a positive integer"
6932
+ );
6866
6933
  validateOptionalBoolean(section2, "dumpOnBlock", "doWork.dumpOnBlock");
6867
6934
  validateOptionalBoolean(section2, "postRunLog", "doWork.postRunLog");
6868
6935
  validateProtectedBranches(section2["protectedBranches"]);
@@ -6894,7 +6961,9 @@ function validateSettingContainer(value, container, keys) {
6894
6961
  }
6895
6962
  for (const key2 of Object.keys(value)) {
6896
6963
  if (!keys.includes(key2)) {
6897
- failSettings(`doWork.${container}.${key2} is not a recognised setting; expected one of: ${keys.join(", ")}.`);
6964
+ failSettings(
6965
+ `doWork.${container}.${key2} is not a recognised setting; expected one of: ${keys.join(", ")}.`
6966
+ );
6898
6967
  }
6899
6968
  }
6900
6969
  }
@@ -6906,7 +6975,11 @@ function orphanPassEnabled(settings) {
6906
6975
  }
6907
6976
  function discoverIssues(settings) {
6908
6977
  if (!issuePassEnabled(settings)) return [];
6909
- const candidates = listCandidateIssues(settings.technique, settings.discoveryValue, settings.limit);
6978
+ const candidates = listCandidateIssues(
6979
+ settings.technique,
6980
+ settings.discoveryValue,
6981
+ settings.limit
6982
+ );
6910
6983
  if (settings.onlyIssue === void 0) {
6911
6984
  if (candidates.length === settings.limit) {
6912
6985
  progress(
@@ -7003,11 +7076,15 @@ function claimIssue(item, settings) {
7003
7076
  if (!item.needsAssignment || item.issue === null) return;
7004
7077
  try {
7005
7078
  assignIssueToAgent(item.issue.number, settings.participants.agentUser);
7006
- progress(` assigned issue #${String(item.issue.number)} to ${settings.participants.agentUser}.
7007
- `);
7079
+ progress(
7080
+ ` assigned issue #${String(item.issue.number)} to ${settings.participants.agentUser}.
7081
+ `
7082
+ );
7008
7083
  } catch (err) {
7009
- progress(` warning: could not assign issue #${String(item.issue.number)}: ${err.message}
7010
- `);
7084
+ progress(
7085
+ ` warning: could not assign issue #${String(item.issue.number)}: ${err.message}
7086
+ `
7087
+ );
7011
7088
  }
7012
7089
  }
7013
7090
  function claimPr(prNumber, agentUser) {
@@ -7016,8 +7093,10 @@ function claimPr(prNumber, agentUser) {
7016
7093
  progress(` assigned pull request #${String(prNumber)} to ${agentUser}.
7017
7094
  `);
7018
7095
  } catch (err) {
7019
- progress(` warning: could not assign pull request #${String(prNumber)}: ${err.message}
7020
- `);
7096
+ progress(
7097
+ ` warning: could not assign pull request #${String(prNumber)}: ${err.message}
7098
+ `
7099
+ );
7021
7100
  }
7022
7101
  }
7023
7102
  function notePickupOnIssue(item, marker) {
@@ -7030,13 +7109,17 @@ function notePickupOnIssue(item, marker) {
7030
7109
  `automata do-work: picked this up on pull request #${String(item.pr.number)} \u2014 ${item.pr.url}`
7031
7110
  );
7032
7111
  } catch (err) {
7033
- progress(` skipped: could not note the pickup on issue #${String(item.issue.number)} \u2014 ${err.message}
7034
- `);
7112
+ progress(
7113
+ ` skipped: could not note the pickup on issue #${String(item.issue.number)} \u2014 ${err.message}
7114
+ `
7115
+ );
7035
7116
  try {
7036
7117
  deleteMarker(marker);
7037
7118
  } catch (deleteErr) {
7038
- progress(` warning: could not withdraw the working marker: ${deleteErr.message}
7039
- `);
7119
+ progress(
7120
+ ` warning: could not withdraw the working marker: ${deleteErr.message}
7121
+ `
7122
+ );
7040
7123
  }
7041
7124
  return false;
7042
7125
  }
@@ -7049,8 +7132,10 @@ function reportIssueMessagesBuriedByNote(item, note, participants) {
7049
7132
  const messages = getIssueSurface(item.issue.number).messages;
7050
7133
  buried = messagesBetween(messages, participants, watermark, note.createdAt);
7051
7134
  } catch (err) {
7052
- progress(` warning: could not re-read issue #${String(item.issue.number)}: ${err.message}
7053
- `);
7135
+ progress(
7136
+ ` warning: could not re-read issue #${String(item.issue.number)}: ${err.message}
7137
+ `
7138
+ );
7054
7139
  return 0;
7055
7140
  }
7056
7141
  if (buried.length === 0) return 0;
@@ -7062,8 +7147,10 @@ function reportIssueMessagesBuriedByNote(item, note, participants) {
7062
7147
  `automata do-work: ${authors} posted here while this issue was being picked up, so ${buried.length === 1 ? "that message was" : "those messages were"} not included in the run. Please post again to have them acted on.`
7063
7148
  );
7064
7149
  } catch (err) {
7065
- progress(` warning: could not report the buried issue message(s): ${err.message}
7066
- `);
7150
+ progress(
7151
+ ` warning: could not report the buried issue message(s): ${err.message}
7152
+ `
7153
+ );
7067
7154
  }
7068
7155
  return buried.length;
7069
7156
  }
@@ -7081,7 +7168,9 @@ function refreshOrphanItem(item, linkMap, settings, policy) {
7081
7168
  const number = item.pr?.number ?? 0;
7082
7169
  const stillOrphan = linkMap.orphans.some((candidate) => candidate.pr.number === number);
7083
7170
  if (!stillOrphan) {
7084
- const linkedTo = issuesClosedBy(linkMap, number).map((issueNumber) => `#${String(issueNumber)}`);
7171
+ const linkedTo = issuesClosedBy(linkMap, number).map(
7172
+ (issueNumber) => `#${String(issueNumber)}`
7173
+ );
7085
7174
  return {
7086
7175
  kind: "skip",
7087
7176
  issue: null,
@@ -7123,7 +7212,8 @@ function reportOvertakenMessages(item, analysis) {
7123
7212
  `);
7124
7213
  }
7125
7214
  }
7126
- async function reconcileMarker(item, marker, participants, watermark, runError, diagnostics, recovery = null) {
7215
+ async function reconcileMarker(item, marker, participants, watermark, run5) {
7216
+ const { runError, diagnostics, recovery, claudeAuth } = run5;
7127
7217
  let analysis;
7128
7218
  try {
7129
7219
  analysis = analyseAnswer(readAnsweringSurface(item), participants, marker, watermark);
@@ -7136,7 +7226,8 @@ async function reconcileMarker(item, marker, participants, watermark, runError,
7136
7226
  }
7137
7227
  let recoveryNote = null;
7138
7228
  let recovered = false;
7139
- if (analysis.answeredAt === null && runError === null && recovery !== null) {
7229
+ const canRecover = runError === null && recovery !== null && claudeAuth === null;
7230
+ if (analysis.answeredAt === null && canRecover) {
7140
7231
  const attempt = await attemptRecovery(item, marker, participants, watermark, recovery);
7141
7232
  if ("analysis" in attempt) {
7142
7233
  analysis = attempt.analysis;
@@ -7146,31 +7237,74 @@ async function reconcileMarker(item, marker, participants, watermark, runError,
7146
7237
  }
7147
7238
  }
7148
7239
  if (analysis.answeredAt !== null) {
7149
- reportOvertakenMessages(item, analysis);
7150
- try {
7151
- deleteMarker(marker);
7152
- } catch (err) {
7153
- progress(` warning: could not delete the marker comment: ${err.message}
7154
- `);
7155
- }
7156
- if (analysis.missed.length > 0) {
7157
- return {
7158
- outcome: "answered-no-reply",
7159
- detail: `answered (${String(analysis.toReport.length)} message(s) arrived mid-run and were flagged)`,
7160
- reason: "flagged"
7161
- };
7162
- }
7163
- if (recovered) {
7164
- return { outcome: "answered", detail: "answered (recovered from the run transcript)", reason: "answered" };
7165
- }
7166
- return runError === null ? { outcome: "answered", detail: "answered", reason: "answered" } : { outcome: "answered", detail: `answered, but the run reported: ${runError.message}`, reason: "answered" };
7240
+ return reportAnswered(item, marker, analysis, runError, recovered);
7167
7241
  }
7242
+ if (claudeAuth !== null) return reportClaudeAuth(item, marker, participants, claudeAuth);
7168
7243
  return reportNoAnswer(item, marker, runError, diagnostics, recoveryNote);
7169
7244
  }
7245
+ function reportAnswered(item, marker, analysis, runError, recovered) {
7246
+ reportOvertakenMessages(item, analysis);
7247
+ try {
7248
+ deleteMarker(marker);
7249
+ } catch (err) {
7250
+ progress(` warning: could not delete the marker comment: ${err.message}
7251
+ `);
7252
+ }
7253
+ if (analysis.missed.length > 0) {
7254
+ return {
7255
+ outcome: "answered-no-reply",
7256
+ detail: `answered (${String(analysis.toReport.length)} message(s) arrived mid-run and were flagged)`,
7257
+ reason: "flagged"
7258
+ };
7259
+ }
7260
+ if (recovered) {
7261
+ return {
7262
+ outcome: "answered",
7263
+ detail: "answered (recovered from the run transcript)",
7264
+ reason: "answered"
7265
+ };
7266
+ }
7267
+ return runError === null ? { outcome: "answered", detail: "answered", reason: "answered" } : {
7268
+ outcome: "answered",
7269
+ detail: `answered, but the run reported: ${runError.message}`,
7270
+ reason: "answered"
7271
+ };
7272
+ }
7273
+ function hasClaudeAuthComment(item, marker, participants) {
7274
+ try {
7275
+ const before = new Date(marker.createdAt).getTime();
7276
+ const agent = participants.agentUser.toLowerCase();
7277
+ const last = readAnsweringSurface(item).findLast(
7278
+ (message) => message.author.toLowerCase() === agent && new Date(message.createdAt).getTime() < before
7279
+ );
7280
+ return last?.body.includes(CLAUDE_AUTH_MARKER) === true;
7281
+ } catch {
7282
+ return false;
7283
+ }
7284
+ }
7285
+ function reportClaudeAuth(item, marker, participants, excerpt) {
7286
+ const surface = markerSurfaceLabel(item);
7287
+ const result = {
7288
+ outcome: "failed",
7289
+ detail: `${CLAUDE_AUTH_REASON}. ${CLAUDE_AUTH_REMEDY}`,
7290
+ reason: "no-answer",
7291
+ claudeAuth: true
7292
+ };
7293
+ const alreadyTold = hasClaudeAuthComment(item, marker, participants);
7294
+ try {
7295
+ if (alreadyTold) deleteMarker(marker);
7296
+ else updateMarker(marker, renderClaudeAuthComment(surface, excerpt));
7297
+ } catch (err) {
7298
+ warnMarkerNotUpdated(surface, err, "that Claude Code is not signed in");
7299
+ }
7300
+ return result;
7301
+ }
7170
7302
  async function attemptRecovery(item, marker, participants, watermark, recovery) {
7171
7303
  const surface = markerSurfaceLabel(item);
7172
- progress(` recovery: the run posted no answer on ${surface}; asking the model to write it from the transcript.
7173
- `);
7304
+ progress(
7305
+ ` recovery: the run posted no answer on ${surface}; asking the model to write it from the transcript.
7306
+ `
7307
+ );
7174
7308
  const reread = () => analyseAnswer(readAnsweringSurface(item), participants, marker, watermark);
7175
7309
  const fail3 = (reason) => {
7176
7310
  progress(` recovery: failed \u2014 ${reason}
@@ -7202,12 +7336,15 @@ async function attemptRecovery(item, marker, participants, watermark, recovery)
7202
7336
  }
7203
7337
  try {
7204
7338
  const verified2 = reread();
7205
- if (verified2.answeredAt === null) return fail3(`the recovered answer is not visible on ${surface} after posting`);
7339
+ if (verified2.answeredAt === null)
7340
+ return fail3(`the recovered answer is not visible on ${surface} after posting`);
7206
7341
  progress(` recovery: posted the answer recovered from the run transcript on ${surface}.
7207
7342
  `);
7208
7343
  return { analysis: verified2, posted: true };
7209
7344
  } catch (err) {
7210
- return fail3(`could not re-read ${surface} to verify the recovered answer: ${err.message}`);
7345
+ return fail3(
7346
+ `could not re-read ${surface} to verify the recovered answer: ${err.message}`
7347
+ );
7211
7348
  }
7212
7349
  }
7213
7350
  function reportUnverified(item, marker) {
@@ -7227,6 +7364,13 @@ function reportUnverified(item, marker) {
7227
7364
  reason: "unverified"
7228
7365
  };
7229
7366
  }
7367
+ function warnMarkerNotUpdated(surface, err, what) {
7368
+ progress(
7369
+ ` warning: could not update the marker comment on ${surface}: ${err.message}
7370
+ the humans have not been told ${what}.
7371
+ `
7372
+ );
7373
+ }
7230
7374
  async function reportNoAnswer(item, marker, runError, diagnostics, recoveryNote = null) {
7231
7375
  const surface = markerSurfaceLabel(item);
7232
7376
  const sideEffects = item.turn === "issue-discuss" ? "It may still have created a branch or opened a pull request \u2014 check before assuming otherwise." : `It may still have changed the branch \`${item.branch}\` \u2014 check it before assuming otherwise.`;
@@ -7235,13 +7379,13 @@ async function reportNoAnswer(item, marker, runError, diagnostics, recoveryNote
7235
7379
  try {
7236
7380
  updateMarker(marker, explanation + recovery + (await diagnostics?.() ?? ""));
7237
7381
  } catch (err) {
7238
- progress(
7239
- ` warning: could not update the marker comment on ${surface}: ${err.message}
7240
- the humans have not been told that this run produced no answer.
7241
- `
7242
- );
7382
+ warnMarkerNotUpdated(surface, err, "that this run produced no answer");
7243
7383
  }
7244
- return runError === null ? { outcome: "answered-no-reply", detail: "run finished but posted no answer", reason: "no-answer" } : { outcome: "failed", detail: `run failed: ${runError.message}`, reason: "no-answer" };
7384
+ return runError === null ? {
7385
+ outcome: "answered-no-reply",
7386
+ detail: "run finished but posted no answer",
7387
+ reason: "no-answer"
7388
+ } : { outcome: "failed", detail: `run failed: ${runError.message}`, reason: "no-answer" };
7245
7389
  }
7246
7390
  async function invokeExecutor(prompt, execution, silent, sink2) {
7247
7391
  beat({
@@ -7253,7 +7397,12 @@ async function invokeExecutor(prompt, execution, silent, sink2) {
7253
7397
  await runCodex(prompt, { model: execution.model, effort: execution.effort, sink: sink2 });
7254
7398
  return;
7255
7399
  }
7256
- await runClaude(prompt, { model: execution.model, effort: execution.effort, printSteps: !silent, sink: sink2 });
7400
+ await runClaude(prompt, {
7401
+ model: execution.model,
7402
+ effort: execution.effort,
7403
+ printSteps: !silent,
7404
+ sink: sink2
7405
+ });
7257
7406
  }
7258
7407
  function repairIssueLink(item, baseBranch, agentUser) {
7259
7408
  const issue = item.issue;
@@ -7261,8 +7410,10 @@ function repairIssueLink(item, baseBranch, agentUser) {
7261
7410
  try {
7262
7411
  const branch = getCurrentBranch();
7263
7412
  if (branch === baseBranch) {
7264
- progress(` issue #${String(issue.number)} is still in discussion (no branch was created).
7265
- `);
7413
+ progress(
7414
+ ` issue #${String(issue.number)} is still in discussion (no branch was created).
7415
+ `
7416
+ );
7266
7417
  return false;
7267
7418
  }
7268
7419
  const pr = getCurrentBranchPr();
@@ -7274,10 +7425,15 @@ function repairIssueLink(item, baseBranch, agentUser) {
7274
7425
  if (pr.assignees.length === 0) {
7275
7426
  claimPr(pr.number, agentUser);
7276
7427
  }
7277
- const closesRef = new RegExp(String.raw`\bcloses\s+#` + String(issue.number) + String.raw`\b`, "i");
7428
+ const closesRef = new RegExp(
7429
+ String.raw`\bcloses\s+#` + String(issue.number) + String.raw`\b`,
7430
+ "i"
7431
+ );
7278
7432
  if (closesRef.test(pr.body)) {
7279
- progress(` pull request #${String(pr.number)} already closes issue #${String(issue.number)}.
7280
- `);
7433
+ progress(
7434
+ ` pull request #${String(pr.number)} already closes issue #${String(issue.number)}.
7435
+ `
7436
+ );
7281
7437
  return true;
7282
7438
  }
7283
7439
  addClosesRefToPr(pr.number, issue.number);
@@ -7285,8 +7441,10 @@ function repairIssueLink(item, baseBranch, agentUser) {
7285
7441
  `);
7286
7442
  return true;
7287
7443
  } catch (err) {
7288
- progress(` warning: could not link a pull request to issue #${String(issue.number)}: ${err.message}
7289
- `);
7444
+ progress(
7445
+ ` warning: could not link a pull request to issue #${String(issue.number)}: ${err.message}
7446
+ `
7447
+ );
7290
7448
  return false;
7291
7449
  }
7292
7450
  }
@@ -7375,8 +7533,10 @@ ${itemLabel(planned)} ${planned.turn}: ${planned.reason}
7375
7533
  }
7376
7534
  let buriedByNote = 0;
7377
7535
  if (note !== null) {
7378
- progress(` noted on issue #${String(item.issue?.number ?? 0)} that the work is on pull request #${String(item.pr?.number ?? 0)}.
7379
- `);
7536
+ progress(
7537
+ ` noted on issue #${String(item.issue?.number ?? 0)} that the work is on pull request #${String(item.pr?.number ?? 0)}.
7538
+ `
7539
+ );
7380
7540
  buriedByNote = reportIssueMessagesBuriedByNote(item, note, settings.participants);
7381
7541
  }
7382
7542
  const watermark = promptWatermark([
@@ -7411,43 +7571,64 @@ ${itemLabel(planned)} ${planned.turn}: ${planned.reason}
7411
7571
  );
7412
7572
  }
7413
7573
  const execution = toExecution(resolved);
7414
- const { runError, diagnostics, recovery } = await runRecorded(item, prompt, execution, settings, silent);
7574
+ const { runError, diagnostics, recovery, claudeAuth } = await runRecorded(
7575
+ item,
7576
+ prompt,
7577
+ execution,
7578
+ settings,
7579
+ silent
7580
+ );
7415
7581
  const ranExecutor = true;
7416
7582
  let reconciled;
7417
7583
  try {
7418
- reconciled = await reconcileMarker(
7419
- item,
7420
- marker,
7421
- settings.participants,
7422
- watermark,
7584
+ reconciled = await reconcileMarker(item, marker, settings.participants, watermark, {
7423
7585
  runError,
7424
7586
  diagnostics,
7425
- recovery
7426
- );
7587
+ recovery,
7588
+ claudeAuth
7589
+ });
7427
7590
  } finally {
7428
7591
  inFlightMarker = null;
7429
7592
  }
7430
7593
  progress(` ${reconciled.detail}
7431
7594
  `);
7432
7595
  const outcome = adjustOutcome(reconciled, item, settings, buriedByNote);
7433
- return { ...base, outcome: outcome.outcome, detail: outcome.detail, ranExecutor, execution };
7596
+ return {
7597
+ ...base,
7598
+ outcome: outcome.outcome,
7599
+ detail: outcome.detail,
7600
+ ranExecutor,
7601
+ execution,
7602
+ claudeAuth: outcome.claudeAuth
7603
+ };
7434
7604
  }
7435
7605
  async function runRecorded(item, prompt, execution, settings, silent) {
7436
7606
  const transcript = settings.postRunLog ? new RunTranscript(transcriptLabel(item)) : null;
7437
7607
  const before = transcript === null ? null : snapshotCheckout();
7608
+ const authTail = transcript === null && execution.executor === "claude" ? new ClaudeAuthTail() : null;
7438
7609
  let runError = null;
7439
7610
  try {
7440
- await invokeExecutor(prompt, execution, silent, transcript ?? void 0);
7611
+ await invokeExecutor(prompt, execution, silent, transcript ?? authTail ?? void 0);
7441
7612
  } catch (err) {
7442
7613
  runError = err;
7443
7614
  }
7444
- if (transcript === null) return { runError, diagnostics: null, recovery: null };
7615
+ if (transcript === null) {
7616
+ const claudeAuth2 = runError !== null && authTail !== null && detectClaudeAuthFailure(authTail.tail) ? "" : null;
7617
+ return { runError, diagnostics: null, recovery: null, claudeAuth: claudeAuth2 };
7618
+ }
7619
+ const tail = transcript.excerpt();
7620
+ const claudeAuth = runError !== null && execution.executor === "claude" && detectClaudeAuthFailure(tail) ? tail : null;
7445
7621
  return {
7446
7622
  runError,
7623
+ claudeAuth,
7447
7624
  recovery: async () => {
7448
7625
  const text = (transcript.saved ? readTranscriptTail(transcript.path) : null) ?? transcript.excerpt();
7449
7626
  if (text.trim() === "") return { ok: false, reason: "the run transcript is empty" };
7450
- return recoverAnswer(execution, { subject: markerSurfaceLabel(item), transcript: text, conversation: prompt });
7627
+ return recoverAnswer(execution, {
7628
+ subject: markerSurfaceLabel(item),
7629
+ transcript: text,
7630
+ conversation: prompt
7631
+ });
7451
7632
  },
7452
7633
  diagnostics: async () => {
7453
7634
  const excerpt = transcript.excerpt();
@@ -7527,8 +7708,10 @@ function summarize(reports) {
7527
7708
  }
7528
7709
  for (const report of reports) {
7529
7710
  const ran = report.execution === void 0 ? "" : ` \xB7 ${describeExecution(report.execution)}`;
7530
- out(` ${reportLabel(report)} ${report.turn ?? "-"} ${report.outcome} \u2014 ${report.detail}${ran}
7531
- `);
7711
+ out(
7712
+ ` ${reportLabel(report)} ${report.turn ?? "-"} ${report.outcome} \u2014 ${report.detail}${ran}
7713
+ `
7714
+ );
7532
7715
  }
7533
7716
  }
7534
7717
  function toItemJson(report) {
@@ -7550,7 +7733,13 @@ function toItemJson(report) {
7550
7733
  }
7551
7734
  var doWorkCommand = new Command6("do-work").description(
7552
7735
  "Run one tick of the autonomous loop: answer the issues whose newest authorized message the agent has not answered, then the open pull requests that close no issue of this repository and have one"
7553
- ).option("--with <executor>", "Executor to use: claude or codex (default: from config, else claude)").option("--model <string>", "Model identifier to pass to the executor, overriding the configured default for it").option(
7736
+ ).option(
7737
+ "--with <executor>",
7738
+ "Executor to use: claude or codex (default: from config, else claude)"
7739
+ ).option(
7740
+ "--model <string>",
7741
+ "Model identifier to pass to the executor, overriding the configured default for it"
7742
+ ).option(
7554
7743
  "--effort <level>",
7555
7744
  "Reasoning effort to pass to the executor, overriding the configured default for it"
7556
7745
  ).option("--issue <number>", "Restrict the tick to a single issue").option(
@@ -7616,7 +7805,12 @@ async function runDoWorkTraced(options) {
7616
7805
  return exitCode2;
7617
7806
  }
7618
7807
  const { exitCode, reports } = await runTickUnderLock(lock.handle, settings, options);
7619
- logTick(reports, exitCode, startedAt);
7808
+ logTick(
7809
+ reports,
7810
+ exitCode,
7811
+ startedAt,
7812
+ reports.some((report) => report.claudeAuth === true) ? "claude-auth" : void 0
7813
+ );
7620
7814
  return exitCode;
7621
7815
  }
7622
7816
  async function runTickUnderLock(handle, settings, options) {
@@ -7682,6 +7876,20 @@ function buildSelectionEvidence(settings, issues, linkMap, decisions, verbose) {
7682
7876
  })) : null
7683
7877
  };
7684
7878
  }
7879
+ function decideAll(settings, issues, linkMap, policy) {
7880
+ return skipDuplicateHeadBranches([
7881
+ ...issues.map(
7882
+ (issue) => decideWork(buildIssueState(issue, linkMap), settings.participants, policy)
7883
+ ),
7884
+ ...discoverOrphanPrs(settings, linkMap).map(
7885
+ (candidate) => decideOrphanPrWork(
7886
+ { prSurface: getPrSurface(candidate.pr.number) },
7887
+ settings.participants,
7888
+ policy
7889
+ )
7890
+ )
7891
+ ]);
7892
+ }
7685
7893
  function collectSelection(settings, verbose) {
7686
7894
  const issues = discoverIssues(settings);
7687
7895
  const linkMap = getOpenPrLinkMap();
@@ -7690,12 +7898,7 @@ function collectSelection(settings, verbose) {
7690
7898
  defaultBranch: linkMap.defaultBranch,
7691
7899
  protectedBranches: settings.protectedBranches
7692
7900
  };
7693
- const decisions = skipDuplicateHeadBranches([
7694
- ...issues.map((issue) => decideWork(buildIssueState(issue, linkMap), settings.participants, policy)),
7695
- ...discoverOrphanPrs(settings, linkMap).map(
7696
- (candidate) => decideOrphanPrWork({ prSurface: getPrSurface(candidate.pr.number) }, settings.participants, policy)
7697
- )
7698
- ]);
7901
+ const decisions = decideAll(settings, issues, linkMap, policy);
7699
7902
  return buildSelectionEvidence(settings, issues, linkMap, decisions, verbose);
7700
7903
  }
7701
7904
  function makeSelectionSection(lines, problems, data) {
@@ -7727,7 +7930,12 @@ function describeIssueEvidence(settings, evidence) {
7727
7930
  `discovery query: ${settings.technique} = ${settings.discoveryValue}, limit ${String(settings.limit)}${restriction}`
7728
7931
  ];
7729
7932
  if (evidence.issues === null) return lines;
7730
- lines.push(heading(`discovery returned ${String(evidence.issues.length)} issue(s)`, evidence.issues.length));
7933
+ lines.push(
7934
+ heading(
7935
+ `discovery returned ${String(evidence.issues.length)} issue(s)`,
7936
+ evidence.issues.length
7937
+ )
7938
+ );
7731
7939
  for (const issue of evidence.issues) {
7732
7940
  lines.push(` #${String(issue.number)} ${issue.title}`);
7733
7941
  }
@@ -7809,11 +8017,11 @@ function selectionData(evidence) {
7809
8017
  }
7810
8018
  function selectionSectionFromEvidence(settings, evidence, verbose) {
7811
8019
  if (evidence === null) {
7812
- return makeSelectionSection(
7813
- ["not run: the tick was blocked before discovery"],
7814
- [],
7815
- { ran: false, detail: "blocked before discovery", plan: [] }
7816
- );
8020
+ return makeSelectionSection(["not run: the tick was blocked before discovery"], [], {
8021
+ ran: false,
8022
+ detail: "blocked before discovery",
8023
+ plan: []
8024
+ });
7817
8025
  }
7818
8026
  const lines = [];
7819
8027
  if (verbose && settings !== null) describeSelectionEvidence(settings, evidence, lines);
@@ -8048,6 +8256,9 @@ function dumpBlocked(options, context) {
8048
8256
  }
8049
8257
  }
8050
8258
  function classifyBlocked(hygiene, decisions, items, reports) {
8259
+ if (reports.some((report) => report.claudeAuth === true)) {
8260
+ return { reason: "claude-auth", trigger: `${CLAUDE_AUTH_REASON}; run \`claude /login\` again` };
8261
+ }
8051
8262
  const ran = reports.some((report) => report.ranExecutor === true);
8052
8263
  if (ran) return null;
8053
8264
  if (hygiene.degraded) {
@@ -8165,12 +8376,7 @@ async function runTick(settings, options) {
8165
8376
  defaultBranch: linkMap.defaultBranch,
8166
8377
  protectedBranches: settings.protectedBranches
8167
8378
  };
8168
- const decisions = skipDuplicateHeadBranches([
8169
- ...issues.map((issue) => decideWork(buildIssueState(issue, linkMap), settings.participants, policy)),
8170
- ...discoverOrphanPrs(settings, linkMap).map(
8171
- (candidate) => decideOrphanPrWork({ prSurface: getPrSurface(candidate.pr.number) }, settings.participants, policy)
8172
- )
8173
- ]);
8379
+ const decisions = decideAll(settings, issues, linkMap, policy);
8174
8380
  const items = decisions.flatMap((decision) => decision.kind === "work" ? [decision.item] : []);
8175
8381
  const planText = `Work plan (${String(items.length)} of ${String(decisions.length)} candidates need an answer):
8176
8382
  ${describePlan(decisions)}`;
@@ -8219,9 +8425,23 @@ async function processItems(items, settings, options, hygiene) {
8219
8425
  const report = await reportForItem(item, settings, options, preflightCauses);
8220
8426
  if (report.ranExecutor === true) runsUsed++;
8221
8427
  reports.push(report);
8428
+ if (report.claudeAuth === true) {
8429
+ reports.push(...items.slice(index + 1).map((rest) => skipForClaudeAuth(rest)));
8430
+ break;
8431
+ }
8222
8432
  }
8223
8433
  return { reports, deferred };
8224
8434
  }
8435
+ function skipForClaudeAuth(item) {
8436
+ return {
8437
+ issue: item.issue?.number ?? null,
8438
+ pr: item.pr?.number ?? null,
8439
+ title: itemTitle(item),
8440
+ turn: item.turn,
8441
+ outcome: "skipped",
8442
+ detail: `${CLAUDE_AUTH_REASON}; run \`claude /login\` again`
8443
+ };
8444
+ }
8225
8445
  async function reportForItem(item, settings, options, preflightCauses) {
8226
8446
  try {
8227
8447
  return await processItem(item, settings, options.silent === true, preflightCauses);
@@ -8892,6 +9112,48 @@ function withoutWatched(list, id) {
8892
9112
  return list.filter((entry) => entry !== id);
8893
9113
  }
8894
9114
 
9115
+ // src/conductor/permissionDenials.ts
9116
+ var MAX_TARGET_LENGTH = 300;
9117
+ function targetOf(input) {
9118
+ if (typeof input !== "object" || input === null) return "";
9119
+ const record = input;
9120
+ for (const key2 of ["command", "file_path", "path", "pattern"]) {
9121
+ const value = record[key2];
9122
+ if (typeof value === "string") return value;
9123
+ }
9124
+ return JSON.stringify(input);
9125
+ }
9126
+ function denialOf(entry) {
9127
+ if (typeof entry !== "object" || entry === null) return null;
9128
+ const record = entry;
9129
+ const tool = record["tool_name"];
9130
+ if (typeof tool !== "string") return null;
9131
+ return { tool, target: targetOf(record["tool_input"]) };
9132
+ }
9133
+ function parsePermissionDenials(stdout) {
9134
+ const denials = [];
9135
+ for (const line of stdout.split("\n")) {
9136
+ if (!line.trim().startsWith("{")) continue;
9137
+ try {
9138
+ const event = JSON.parse(line);
9139
+ const listed = event["type"] === "result" ? event["permission_denials"] : void 0;
9140
+ if (!Array.isArray(listed)) continue;
9141
+ for (const entry of listed) {
9142
+ const denial = denialOf(entry);
9143
+ if (denial !== null) denials.push(denial);
9144
+ }
9145
+ } catch {
9146
+ }
9147
+ }
9148
+ return denials;
9149
+ }
9150
+ function describePermissionDenials(denials) {
9151
+ return denials.map((denial) => {
9152
+ const flat = `${denial.tool} ${denial.target}`.replace(/[\u0000-\u001f\u007f-\u009f]+/g, " ").replace(/\s+/g, " ").trim();
9153
+ return flat.length <= MAX_TARGET_LENGTH ? flat : `${flat.slice(0, MAX_TARGET_LENGTH)}\u2026`;
9154
+ });
9155
+ }
9156
+
8895
9157
  // src/commands/conductor.ts
8896
9158
  function fail2(message) {
8897
9159
  process.stderr.write(`Error: ${message}
@@ -8956,7 +9218,29 @@ function readTargetMessages(target) {
8956
9218
  }
8957
9219
  function runModel(prompt, execution, replyTo, sink2) {
8958
9220
  const { owner, repo } = getRepoSlug();
8959
- return runClaude(prompt, { model: execution.model, effort: execution.effort, readOnly: true, replyTo, ghRepo: `${owner}/${repo}`, sink: sink2 });
9221
+ return runClaude(prompt, { model: execution.model, effort: execution.effort, readOnly: true, replyTo, ghRepo: `${owner}/${repo}`, sink: sink2, printSteps: verboseRun, echoResult: false });
9222
+ }
9223
+ var verboseRun = false;
9224
+ function reportDenials(stdout, label2) {
9225
+ if (!verboseRun || stdout.length === 0) return;
9226
+ const lines = describePermissionDenials(parsePermissionDenials(stdout));
9227
+ if (lines.length === 0) {
9228
+ process.stderr.write(`Conductor: the run for ${label2} reported no denied tool call.
9229
+ `);
9230
+ return;
9231
+ }
9232
+ process.stderr.write(`Conductor: the run for ${label2} was denied ${String(lines.length)} tool call(s):
9233
+ `);
9234
+ for (const line of lines) process.stderr.write(` - ${line}
9235
+ `);
9236
+ }
9237
+ function reportCommandTrace() {
9238
+ const trace = takeCommandTrace();
9239
+ if (trace === null) return;
9240
+ process.stderr.write(`Commands (${String(trace.length)})
9241
+ `);
9242
+ for (const traced of trace) process.stderr.write(` ${describeTracedCommand(traced)}
9243
+ `);
8960
9244
  }
8961
9245
  function captureStdout() {
8962
9246
  let stdout = "";
@@ -8971,8 +9255,12 @@ function captureStdout() {
8971
9255
  async function runModelForText(prompt, execution) {
8972
9256
  const { owner, repo } = getRepoSlug();
8973
9257
  const captured = captureStdout();
8974
- const options = { model: execution.model, effort: execution.effort, readOnly: true, ghRepo: `${owner}/${repo}`, sink: captured.sink };
8975
- await runClaude(prompt, options);
9258
+ const options = { model: execution.model, effort: execution.effort, readOnly: true, ghRepo: `${owner}/${repo}`, sink: captured.sink, printSteps: verboseRun, echoResult: false };
9259
+ try {
9260
+ await runClaude(prompt, options);
9261
+ } finally {
9262
+ reportDenials(captured.text(), "the dry run");
9263
+ }
8976
9264
  return extractDryRunReply(execution.executor, captured.text());
8977
9265
  }
8978
9266
  function coveredOf(watch, linkMap) {
@@ -9052,6 +9340,7 @@ async function conductItem(id, config, login2, participants, execution, dryRun,
9052
9340
  read: () => readTargetMessages(replyTo),
9053
9341
  run: () => runModel(prompt, execution, replyTo, captured.sink)
9054
9342
  });
9343
+ reportDenials(captured.text(), label2);
9055
9344
  const needsHuman = parseNeedsHuman(extractDryRunReply(execution.executor, captured.text()) ?? "");
9056
9345
  if (needsHuman !== null && outcome.kind !== "run-failed" && outcome.kind !== "unverified") {
9057
9346
  return blockItem(item, label2, needsHuman);
@@ -9300,6 +9589,17 @@ function reportHeldLock(held, suspect, staleMinutes) {
9300
9589
  return 2;
9301
9590
  }
9302
9591
  async function runConductor(options = {}) {
9592
+ verboseRun = options.verbose === true;
9593
+ if (verboseRun) startCommandTrace();
9594
+ try {
9595
+ return await runConductorTick(options);
9596
+ } finally {
9597
+ reportCommandTrace();
9598
+ verboseRun = false;
9599
+ stopCommandTrace();
9600
+ }
9601
+ }
9602
+ async function runConductorTick(options) {
9303
9603
  if (options.check === true && options.dryRun === true) {
9304
9604
  return fail2("--check and --dry-run cannot be used together.");
9305
9605
  }
@@ -9340,7 +9640,10 @@ async function runConductor(options = {}) {
9340
9640
  }
9341
9641
  var conductorCommand = new Command7("conductor").description(
9342
9642
  "Run one conductor tick as an allowed (human) account: verify the identity, take the conductor's own run lock, then act"
9343
- ).option("--check", "Report what a tick would do, including pending prunes, and change nothing; exit 1 on a problem").option("--dry-run", "Run the model for each owed reply and print it instead of posting; no lock, no prune").action(async (options) => {
9643
+ ).option("--check", "Report what a tick would do, including pending prunes, and change nothing; exit 1 on a problem").option("--dry-run", "Run the model for each owed reply and print it instead of posting; no lock, no prune").option(
9644
+ "--verbose",
9645
+ "Trace every `git` and `gh` call with its duration and exit code, show the model's steps, and list the tool calls the run was denied"
9646
+ ).action(async (options) => {
9344
9647
  exitWith(await runConductor(options));
9345
9648
  });
9346
9649
  conductorCommand.command("add <id>").description("Watch an issue or pull request and make do-work pick it up (an issue also follows its linked PR)").action((id) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "automata-cli",
3
- "version": "0.12.0-develop.533",
3
+ "version": "0.12.0-develop.562",
4
4
  "description": "Automata CLI tool",
5
5
  "type": "module",
6
6
  "engines": {