patchrelay 0.78.1 → 0.80.0
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/dist/agent-input-service.js +1 -1
- package/dist/awaiting-input-reason.js +6 -7
- package/dist/build-info.json +3 -3
- package/dist/git-worktree-status.js +1 -1
- package/dist/idle-reconciliation-helpers.js +2 -4
- package/dist/idle-reconciliation.js +3 -2
- package/dist/issue-execution-state.js +139 -0
- package/dist/issue-session-events.js +219 -53
- package/dist/issue-session-lease-service.js +69 -66
- package/dist/issue-session.js +0 -4
- package/dist/linear-agent-activity-recovery.js +2 -8
- package/dist/operator-retry-event.js +12 -6
- package/dist/paused-issue-state.js +5 -4
- package/dist/prompting/patchrelay.js +47 -80
- package/dist/queue-health-monitor.js +4 -3
- package/dist/run-context.js +382 -0
- package/dist/run-failure-policy.js +2 -1
- package/dist/run-finalizer.js +44 -28
- package/dist/run-launcher.js +3 -5
- package/dist/run-orchestrator.js +10 -14
- package/dist/run-wake-planner.js +40 -30
- package/dist/status-note.js +36 -18
- package/dist/tracked-issue-list-query.js +5 -1
- package/dist/utils.js +9 -0
- package/dist/waiting-reason.js +68 -68
- package/dist/webhooks/issue-update-plan.js +2 -1
- package/dist/workflow-wake-resolver.js +20 -10
- package/package.json +1 -1
package/dist/run-wake-planner.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { getCiRepairBudget, getQueueRepairBudget, getReviewFixBudget, } from "./run-budgets.js";
|
|
2
2
|
import { buildRequestedChangesWakeIdentity } from "./reactive-wake-keys.js";
|
|
3
|
+
import { parseRunContextOrWarn, serializeRunContext } from "./run-context.js";
|
|
4
|
+
import { assertNever } from "./utils.js";
|
|
3
5
|
const WRITER = "run-wake-planner";
|
|
4
6
|
export class RunWakePlanner {
|
|
5
7
|
db;
|
|
6
|
-
|
|
8
|
+
logger;
|
|
9
|
+
constructor(db, logger) {
|
|
7
10
|
this.db = db;
|
|
11
|
+
this.logger = logger;
|
|
8
12
|
}
|
|
9
13
|
resolveRunWake(issue) {
|
|
10
14
|
const sessionWake = this.db.workflowWakes.peekIssueWake(issue.projectId, issue.linearIssueId);
|
|
@@ -22,46 +26,52 @@ export class RunWakePlanner {
|
|
|
22
26
|
let eventType;
|
|
23
27
|
let dedupeKey;
|
|
24
28
|
let eventContext = context;
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
29
|
+
switch (runType) {
|
|
30
|
+
case "queue_repair":
|
|
31
|
+
eventType = "merge_steward_incident";
|
|
32
|
+
dedupeKey = `${dedupeScope ?? "wake"}:queue_repair:${issue.linearIssueId}:${issue.prHeadSha ?? issue.lastGitHubFailureHeadSha ?? "unknown-sha"}`;
|
|
33
|
+
break;
|
|
34
|
+
case "ci_repair":
|
|
35
|
+
eventType = "settled_red_ci";
|
|
36
|
+
dedupeKey = `${dedupeScope ?? "wake"}:ci_repair:${issue.linearIssueId}:${issue.lastGitHubFailureSignature ?? issue.prHeadSha ?? "unknown-sha"}`;
|
|
37
|
+
break;
|
|
38
|
+
case "review_fix":
|
|
39
|
+
case "branch_upkeep": {
|
|
40
|
+
eventType = "review_changes_requested";
|
|
41
|
+
const identity = buildRequestedChangesWakeIdentity({
|
|
42
|
+
linearIssueId: issue.linearIssueId,
|
|
43
|
+
runType,
|
|
44
|
+
headSha: issue.prHeadSha,
|
|
45
|
+
});
|
|
46
|
+
dedupeKey = identity.dedupeKey;
|
|
47
|
+
eventContext = {
|
|
48
|
+
...context,
|
|
49
|
+
requestedChangesCoalesceKey: identity.coalesceKey,
|
|
50
|
+
...(identity.headSha ? { requestedChangesHeadSha: identity.headSha } : {}),
|
|
51
|
+
};
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
case "implementation":
|
|
55
|
+
eventType = "delegated";
|
|
56
|
+
dedupeKey = `${dedupeScope ?? "wake"}:implementation:${issue.linearIssueId}`;
|
|
57
|
+
break;
|
|
58
|
+
default:
|
|
59
|
+
return assertNever(runType, "Unhandled run type in wake event append");
|
|
50
60
|
}
|
|
51
61
|
return Boolean(this.db.issueSessions.appendIssueSessionEventWithLease(lease, {
|
|
52
62
|
projectId: issue.projectId,
|
|
53
63
|
linearIssueId: issue.linearIssueId,
|
|
54
64
|
eventType,
|
|
55
|
-
...(eventContext ? { eventJson:
|
|
65
|
+
...(eventContext ? { eventJson: serializeRunContext(eventContext, "wake event context") } : {}),
|
|
56
66
|
dedupeKey,
|
|
57
67
|
}));
|
|
58
68
|
}
|
|
59
69
|
materializeLegacyPendingWake(issue, lease) {
|
|
60
70
|
if (!issue.pendingRunType)
|
|
61
71
|
return issue;
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
72
|
+
// Boundary over possibly-old DB rows: a legacy pending context that no
|
|
73
|
+
// longer parses is dropped (with a warning) instead of wedging the wake.
|
|
74
|
+
const context = parseRunContextOrWarn(issue.pendingRunContextJson, (message) => this.logger?.warn({ issueKey: issue.issueKey, linearIssueId: issue.linearIssueId }, `Dropping unparseable legacy pending run context: ${message}`), "legacy pending run context");
|
|
65
75
|
this.appendWakeEventWithLease(lease, issue, issue.pendingRunType, context, "legacy_pending");
|
|
66
76
|
const commit = this.db.issueSessions.commitIssueState({
|
|
67
77
|
writer: WRITER,
|
package/dist/status-note.js
CHANGED
|
@@ -1,42 +1,60 @@
|
|
|
1
1
|
import { extractCompletionCheck } from "./completion-check.js";
|
|
2
|
-
import { extractLatestAssistantSummary } from "./issue-session-events.js";
|
|
2
|
+
import { extractLatestAssistantSummary, parseIssueSessionEventOrWarn } from "./issue-session-events.js";
|
|
3
3
|
import { sanitizeOperatorFacingText } from "./presentation-text.js";
|
|
4
|
+
import { assertNever } from "./utils.js";
|
|
4
5
|
function clean(value) {
|
|
5
6
|
return sanitizeOperatorFacingText(value);
|
|
6
7
|
}
|
|
8
|
+
function dirtyWorktreeSummary(payload) {
|
|
9
|
+
return payload?.dirtyWorktree === true ? payload.summary : undefined;
|
|
10
|
+
}
|
|
7
11
|
function eventStatusNote(event) {
|
|
8
12
|
if (!event)
|
|
9
13
|
return undefined;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
// Read-model boundary over possibly-old DB rows: degrade a bad payload to
|
|
15
|
+
// the payload-less note instead of failing the status view.
|
|
16
|
+
const typed = parseIssueSessionEventOrWarn(event);
|
|
17
|
+
if (!typed)
|
|
18
|
+
return undefined;
|
|
19
|
+
switch (typed.eventType) {
|
|
20
|
+
case "stop_requested": {
|
|
21
|
+
const dirtySummary = dirtyWorktreeSummary(typed.payload);
|
|
16
22
|
if (dirtySummary)
|
|
17
23
|
return `Operator stopped the run with dirty worktree: ${dirtySummary}. Use retry or delegate again to resume.`;
|
|
18
24
|
return "Operator stopped the run. Use retry or delegate again to resume.";
|
|
19
|
-
|
|
25
|
+
}
|
|
26
|
+
case "undelegated": {
|
|
27
|
+
const dirtySummary = dirtyWorktreeSummary(typed.payload);
|
|
20
28
|
if (dirtySummary)
|
|
21
29
|
return `Issue was un-delegated from PatchRelay with dirty worktree: ${dirtySummary}`;
|
|
22
30
|
return "Issue was un-delegated from PatchRelay. Delegate it again to resume.";
|
|
31
|
+
}
|
|
23
32
|
case "issue_removed":
|
|
24
33
|
return "Issue was removed from Linear.";
|
|
25
34
|
case "pr_closed":
|
|
26
35
|
return "Pull request was closed without merging.";
|
|
27
36
|
case "pr_merged":
|
|
28
37
|
return "Pull request merged successfully.";
|
|
29
|
-
|
|
38
|
+
case "delegated":
|
|
39
|
+
case "delegation_observed":
|
|
40
|
+
case "child_changed":
|
|
41
|
+
case "child_delivered":
|
|
42
|
+
case "child_regressed":
|
|
43
|
+
case "direct_reply":
|
|
44
|
+
case "completion_check_continue":
|
|
45
|
+
case "followup_prompt":
|
|
46
|
+
case "followup_comment":
|
|
47
|
+
case "prompt_delivered":
|
|
48
|
+
case "self_comment":
|
|
49
|
+
case "operator_prompt":
|
|
50
|
+
case "review_changes_requested":
|
|
51
|
+
case "settled_red_ci":
|
|
52
|
+
case "merge_steward_incident":
|
|
53
|
+
case "operator_closed":
|
|
54
|
+
case "run_released_authority":
|
|
30
55
|
return undefined;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
function parseEventJson(value) {
|
|
34
|
-
try {
|
|
35
|
-
const parsed = JSON.parse(value);
|
|
36
|
-
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : undefined;
|
|
37
|
-
}
|
|
38
|
-
catch {
|
|
39
|
-
return undefined;
|
|
56
|
+
default:
|
|
57
|
+
return assertNever(typed, "Unhandled issue session event in status note");
|
|
40
58
|
}
|
|
41
59
|
}
|
|
42
60
|
export function deriveIssueStatusNote(params) {
|
|
@@ -137,7 +137,11 @@ export class TrackedIssueListQuery {
|
|
|
137
137
|
...(row.last_blocking_review_head_sha !== null ? { lastBlockingReviewHeadSha: String(row.last_blocking_review_head_sha) } : {}),
|
|
138
138
|
...(row.last_github_failure_check_name !== null ? { latestFailureCheckName: String(row.last_github_failure_check_name) } : {}),
|
|
139
139
|
});
|
|
140
|
-
|
|
140
|
+
// The derivation (issue-execution-state.ts via waiting-reason.ts) is the
|
|
141
|
+
// single source; the stored session projection is only a fallback for
|
|
142
|
+
// rows whose live facts derive no reason. A detached active run means
|
|
143
|
+
// the projection is stale, so it is not consulted at all.
|
|
144
|
+
const waitingReason = derivedWaitingReason ?? (detachedActiveRun ? undefined : sessionWaitingReason);
|
|
141
145
|
const latestRun = row.latest_run_type !== null && row.latest_run_status !== null
|
|
142
146
|
? {
|
|
143
147
|
id: 0,
|
package/dist/utils.js
CHANGED
|
@@ -87,6 +87,15 @@ export function safeJsonParse(value) {
|
|
|
87
87
|
return undefined;
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* Exhaustiveness guard for discriminated-union switches (plan §D2): the call
|
|
92
|
+
* only typechecks when every union member is handled, so adding a new variant
|
|
93
|
+
* fails compilation at every consumer. At runtime it throws — reaching it
|
|
94
|
+
* means a value outside the union leaked past a parse boundary.
|
|
95
|
+
*/
|
|
96
|
+
export function assertNever(value, message = "Unexpected value") {
|
|
97
|
+
throw new Error(`${message}: ${JSON.stringify(value)}`);
|
|
98
|
+
}
|
|
90
99
|
export function redactSensitiveHeaders(headers) {
|
|
91
100
|
return Object.fromEntries(Object.entries(headers).map(([name, value]) => [name, REDACTED_HEADER_NAMES.has(name.toLowerCase()) ? "[redacted]" : value]));
|
|
92
101
|
}
|
package/dist/waiting-reason.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { deriveIssueExecutionState, } from "./issue-execution-state.js";
|
|
2
2
|
export const PATCHRELAY_WAITING_REASONS = {
|
|
3
3
|
activeWork: "PatchRelay is actively working",
|
|
4
4
|
automationPaused: "PatchRelay automation is paused because the issue is undelegated",
|
|
@@ -16,82 +16,82 @@ export const PATCHRELAY_WAITING_REASONS = {
|
|
|
16
16
|
waitingForOperatorIntervention: "Waiting on operator intervention",
|
|
17
17
|
waitingForExternalReview: "Waiting on external review",
|
|
18
18
|
};
|
|
19
|
+
/**
|
|
20
|
+
* `waitingReason` is a pure function of {@link IssueExecutionState} — the
|
|
21
|
+
* union (issue-execution-state.ts) is the single derivation of "why is this
|
|
22
|
+
* issue not moving"; this module only renders it for operators.
|
|
23
|
+
*/
|
|
19
24
|
export function derivePatchRelayWaitingReason(params) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
if (params.activeRunId !== undefined) {
|
|
35
|
-
return PATCHRELAY_WAITING_REASONS.activeWork;
|
|
36
|
-
}
|
|
37
|
-
if (params.orchestrationSettleUntil) {
|
|
38
|
-
const settleAt = Date.parse(params.orchestrationSettleUntil);
|
|
39
|
-
if (Number.isFinite(settleAt) && settleAt > Date.now()) {
|
|
25
|
+
return waitingReasonForExecutionState(deriveIssueExecutionState(params));
|
|
26
|
+
}
|
|
27
|
+
export function waitingReasonForExecutionState(state) {
|
|
28
|
+
switch (state.kind) {
|
|
29
|
+
case "undelegated":
|
|
30
|
+
return state.downstreamMayContinue
|
|
31
|
+
? PATCHRELAY_WAITING_REASONS.automationPausedDownstream
|
|
32
|
+
: PATCHRELAY_WAITING_REASONS.automationPaused;
|
|
33
|
+
case "running":
|
|
34
|
+
// An inconsistent row still describes what is observably happening (a run
|
|
35
|
+
// occupies the slot); reconcilers act on the union kind, not this string.
|
|
36
|
+
case "inconsistent":
|
|
37
|
+
return describeRun(state.run);
|
|
38
|
+
case "settling":
|
|
40
39
|
return PATCHRELAY_WAITING_REASONS.waitingForChildSettle;
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
if (blockedByKeys.length > 0) {
|
|
45
|
-
return `Blocked by ${blockedByKeys.join(", ")}`;
|
|
46
|
-
}
|
|
47
|
-
const checkName = params.latestFailureCheckName ?? "CI";
|
|
48
|
-
switch (params.factoryState) {
|
|
49
|
-
case "awaiting_input":
|
|
40
|
+
case "blocked":
|
|
41
|
+
return `Blocked by ${state.blockedByKeys.join(", ")}`;
|
|
42
|
+
case "waiting_input":
|
|
50
43
|
return PATCHRELAY_WAITING_REASONS.waitingForOperatorInput;
|
|
51
|
-
case "
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
return PATCHRELAY_WAITING_REASONS.workComplete;
|
|
61
|
-
case "failed":
|
|
62
|
-
case "escalated":
|
|
63
|
-
return PATCHRELAY_WAITING_REASONS.waitingForOperatorIntervention;
|
|
64
|
-
default:
|
|
44
|
+
case "awaiting_followup":
|
|
45
|
+
switch (state.followup) {
|
|
46
|
+
case "review_fix":
|
|
47
|
+
return PATCHRELAY_WAITING_REASONS.waitingForReviewFeedback;
|
|
48
|
+
case "ci_repair":
|
|
49
|
+
return `Waiting to repair ${state.checkName ?? "CI"}`;
|
|
50
|
+
case "queue_repair":
|
|
51
|
+
return PATCHRELAY_WAITING_REASONS.waitingForMergeStewardRepair;
|
|
52
|
+
}
|
|
65
53
|
break;
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
54
|
+
case "terminal":
|
|
55
|
+
return state.outcome === "done"
|
|
56
|
+
? PATCHRELAY_WAITING_REASONS.workComplete
|
|
57
|
+
: PATCHRELAY_WAITING_REASONS.waitingForOperatorIntervention;
|
|
58
|
+
case "idle_awaiting_external":
|
|
59
|
+
switch (state.waitingOn) {
|
|
60
|
+
case "merge_queue":
|
|
61
|
+
case "downstream_automation":
|
|
62
|
+
return PATCHRELAY_WAITING_REASONS.waitingForDownstreamAutomation;
|
|
63
|
+
case "ci_failure":
|
|
64
|
+
return `${state.checkName ?? "CI"} failed`;
|
|
65
|
+
case "review_of_new_head":
|
|
66
|
+
return PATCHRELAY_WAITING_REASONS.waitingForReviewOnNewHead;
|
|
67
|
+
case "blocking_review_same_head":
|
|
68
|
+
return PATCHRELAY_WAITING_REASONS.sameHeadStillBlocked;
|
|
69
|
+
case "review_feedback":
|
|
70
|
+
return PATCHRELAY_WAITING_REASONS.waitingForReviewFeedback;
|
|
71
|
+
case "external_review":
|
|
72
|
+
return PATCHRELAY_WAITING_REASONS.waitingForExternalReview;
|
|
76
73
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
return PATCHRELAY_WAITING_REASONS.waitingForDownstreamAutomation;
|
|
74
|
+
break;
|
|
75
|
+
case "ready":
|
|
76
|
+
return `Ready to run ${humanize(state.pendingRunType)}`;
|
|
77
|
+
case "idle":
|
|
78
|
+
return undefined;
|
|
83
79
|
}
|
|
84
|
-
|
|
85
|
-
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
function describeRun(run) {
|
|
83
|
+
if (!run.runType) {
|
|
84
|
+
return PATCHRELAY_WAITING_REASONS.activeWork;
|
|
86
85
|
}
|
|
87
|
-
|
|
88
|
-
|
|
86
|
+
switch (run.phase) {
|
|
87
|
+
case "finalizing_published_pr":
|
|
88
|
+
return PATCHRELAY_WAITING_REASONS.finalizingPublishedPr;
|
|
89
|
+
case "finalizing_merged_change":
|
|
90
|
+
return PATCHRELAY_WAITING_REASONS.finalizingMergedChange;
|
|
91
|
+
case "working":
|
|
92
|
+
return `PatchRelay is running ${humanize(run.runType)}`;
|
|
89
93
|
}
|
|
90
|
-
return undefined;
|
|
91
94
|
}
|
|
92
95
|
function humanize(value) {
|
|
93
96
|
return value.replaceAll("_", " ");
|
|
94
97
|
}
|
|
95
|
-
function hasLiveOpenPr(prNumber, prState) {
|
|
96
|
-
return prNumber !== undefined && (prState === undefined || prState === "open");
|
|
97
|
-
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { serializeRunContext } from "../run-context.js";
|
|
1
2
|
/**
|
|
2
3
|
* Picks the single factoryState the caller should write. Priority is the
|
|
3
4
|
* inverse of the previous spread order — the LAST spread wins in JS, so we
|
|
@@ -48,7 +49,7 @@ function buildStartupResumeContextJson(input) {
|
|
|
48
49
|
if (input.startupResume.pendingRunType === undefined)
|
|
49
50
|
return undefined;
|
|
50
51
|
return input.startupResume.pendingRunContext
|
|
51
|
-
?
|
|
52
|
+
? serializeRunContext(input.startupResume.pendingRunContext, "startup resume context")
|
|
52
53
|
: null;
|
|
53
54
|
}
|
|
54
55
|
export function resolveIssueUpdatePlan(input) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { deriveIssueSessionReactiveIntent } from "./issue-session.js";
|
|
2
|
+
import { tryParseRunContextValue } from "./run-context.js";
|
|
2
3
|
function parseObjectJson(raw) {
|
|
3
4
|
if (!raw)
|
|
4
5
|
return undefined;
|
|
@@ -12,6 +13,17 @@ function parseObjectJson(raw) {
|
|
|
12
13
|
return undefined;
|
|
13
14
|
}
|
|
14
15
|
}
|
|
16
|
+
// Boundary over reconciliation columns (failure context / queue incident /
|
|
17
|
+
// CI snapshot JSON) merged into an implicit wake's run context. Degrading a
|
|
18
|
+
// schema-rejected value to "no context" matches the pre-existing behavior of
|
|
19
|
+
// parseObjectJson for malformed JSON; the persistence layer has no logger to
|
|
20
|
+
// warn through.
|
|
21
|
+
function parseRunContextColumn(raw) {
|
|
22
|
+
const value = parseObjectJson(raw);
|
|
23
|
+
if (!value)
|
|
24
|
+
return undefined;
|
|
25
|
+
return tryParseRunContextValue(value);
|
|
26
|
+
}
|
|
15
27
|
function hasUnattemptedFailureSignature(issue, fallbackHeadSha) {
|
|
16
28
|
const signature = issue.lastGitHubFailureSignature;
|
|
17
29
|
if (!signature)
|
|
@@ -33,11 +45,11 @@ export function deriveImplicitReactiveWake(issue) {
|
|
|
33
45
|
if (!reactiveIntent)
|
|
34
46
|
return undefined;
|
|
35
47
|
if (reactiveIntent.runType === "ci_repair") {
|
|
36
|
-
const failureContext =
|
|
37
|
-
const
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
48
|
+
const failureContext = parseRunContextColumn(issue.lastGitHubFailureContextJson) ?? {};
|
|
49
|
+
const snapshotValue = parseObjectJson(issue.lastGitHubCiSnapshotJson);
|
|
50
|
+
const snapshot = snapshotValue ? tryParseRunContextValue({ ciSnapshot: snapshotValue })?.ciSnapshot : undefined;
|
|
51
|
+
const fallbackHeadSha = failureContext.failureHeadSha
|
|
52
|
+
?? issue.lastGitHubFailureHeadSha ?? issue.prHeadSha;
|
|
41
53
|
const failureSignature = issue.lastGitHubFailureSignature
|
|
42
54
|
?? (fallbackHeadSha ? `implicit_branch_ci::${fallbackHeadSha}` : undefined);
|
|
43
55
|
if (!failureSignature || issue.prState !== "open")
|
|
@@ -59,11 +71,9 @@ export function deriveImplicitReactiveWake(issue) {
|
|
|
59
71
|
};
|
|
60
72
|
}
|
|
61
73
|
if (reactiveIntent.runType === "queue_repair") {
|
|
62
|
-
const failureContext =
|
|
63
|
-
const incidentContext =
|
|
64
|
-
const fallbackHeadSha =
|
|
65
|
-
? failureContext.failureHeadSha
|
|
66
|
-
: undefined;
|
|
74
|
+
const failureContext = parseRunContextColumn(issue.lastGitHubFailureContextJson) ?? {};
|
|
75
|
+
const incidentContext = parseRunContextColumn(issue.lastQueueIncidentJson) ?? {};
|
|
76
|
+
const fallbackHeadSha = failureContext.failureHeadSha;
|
|
67
77
|
if (!hasUnattemptedFailureSignature(issue, fallbackHeadSha))
|
|
68
78
|
return undefined;
|
|
69
79
|
return {
|