Skip to content

Commit 223855d

Browse files
author
StackMemory Bot (CLI)
committed
refactor(conductor): extract attemptRun method, add incremental tsc
- Extract duplicated retry logic into attemptRun() method - Eliminates ~40 lines of copy-paste between first attempt and retry - Add incremental: true to tsconfig.json with .tsbuildinfo cache - Add .tsbuildinfo to .gitignore
1 parent e238bed commit 223855d

File tree

3 files changed

+67
-68
lines changed

3 files changed

+67
-68
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ vendor/
55

66
# Build outputs
77
dist/
8+
.tsbuildinfo
89
build/
910
out/
1011
*.out

src/cli/commands/orchestrator.ts

Lines changed: 63 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -518,88 +518,84 @@ export class Conductor {
518518
// 3. Run after_create hook (restore context)
519519
await this.runHook('after-create', workspacePath, issue);
520520

521-
// 4. Spawn agent
522-
run.status = 'running';
523-
await this.runAgent(issue, run);
524-
525-
// 5. Success path
526-
run.status = 'completed';
527-
this.completeCount++;
528-
529-
// Run after_run hook (capture context)
530-
await this.runHook('after-run', workspacePath, issue, run.attempt);
531-
532-
// Take snapshot for session continuity
533-
this.takeSnapshot(workspacePath, issue);
534-
535-
// Move to In Review
536-
await this.transitionIssue(issue, this.config.inReviewState);
537-
538-
console.log(`[${issue.identifier}] Completed successfully`);
521+
// 4. Attempt agent run (with retries)
522+
await this.attemptRun(issue, run);
539523
} catch (err) {
540-
run.status = 'failed';
541-
run.error = (err as Error).message;
524+
this.failCount++;
525+
console.log(`[${issue.identifier}] Failed: ${(err as Error).message}`);
526+
} finally {
527+
this.running.delete(issueId);
528+
// Keep claimed so we don't re-dispatch within this session
529+
}
530+
}
542531

543-
logger.error('Issue dispatch failed', {
544-
identifier: issue.identifier,
545-
error: run.error,
546-
attempt: run.attempt,
547-
});
532+
/**
533+
* Run the agent with retry logic. Throws on final failure.
534+
*/
535+
private async attemptRun(
536+
issue: LinearIssue,
537+
run: RunningIssue
538+
): Promise<void> {
539+
const maxAttempts = this.config.maxRetries + 1;
540+
541+
while (run.attempt <= maxAttempts) {
542+
try {
543+
run.status = 'running';
544+
await this.runAgent(issue, run);
548545

549-
// Run after_run hook even on failure
550-
if (run.workspacePath) {
546+
// Success
547+
run.status = 'completed';
548+
this.completeCount++;
551549
await this.runHook(
552550
'after-run',
553551
run.workspacePath,
554552
issue,
555553
run.attempt
556554
).catch(() => {});
557-
}
558-
559-
// Retry logic
560-
if (run.attempt < this.config.maxRetries + 1) {
555+
this.takeSnapshot(run.workspacePath, issue);
556+
await this.transitionIssue(issue, this.config.inReviewState);
561557
console.log(
562-
`[${issue.identifier}] Failed (attempt ${run.attempt}), retrying...`
558+
run.attempt === 1
559+
? `[${issue.identifier}] Completed successfully`
560+
: `[${issue.identifier}] Completed on retry ${run.attempt}`
563561
);
564-
run.attempt++;
565-
this.totalAttempts++;
562+
return;
563+
} catch (err) {
564+
run.status = 'failed';
565+
run.error = (err as Error).message;
566566

567-
// Exponential backoff
568-
const backoffMs = Math.min(1000 * Math.pow(2, run.attempt - 1), 300000);
569-
await new Promise((r) => setTimeout(r, backoffMs));
567+
logger.error('Agent run failed', {
568+
identifier: issue.identifier,
569+
error: run.error,
570+
attempt: run.attempt,
571+
});
570572

571-
if (!this.stopping) {
572-
try {
573-
run.status = 'running';
574-
await this.runAgent(issue, run);
575-
run.status = 'completed';
576-
this.completeCount++;
577-
await this.runHook(
578-
'after-run',
579-
run.workspacePath,
580-
issue,
581-
run.attempt
582-
).catch(() => {});
583-
await this.transitionIssue(issue, this.config.inReviewState);
584-
console.log(
585-
`[${issue.identifier}] Completed on retry ${run.attempt}`
586-
);
587-
} catch (retryErr) {
588-
run.status = 'failed';
589-
run.error = (retryErr as Error).message;
590-
this.failCount++;
591-
console.log(
592-
`[${issue.identifier}] Failed after ${run.attempt} attempts: ${run.error}`
593-
);
594-
}
573+
// Run after_run hook even on failure
574+
if (run.workspacePath) {
575+
await this.runHook(
576+
'after-run',
577+
run.workspacePath,
578+
issue,
579+
run.attempt
580+
).catch(() => {});
581+
}
582+
583+
// If more attempts remain, retry with backoff
584+
if (run.attempt < maxAttempts && !this.stopping) {
585+
console.log(
586+
`[${issue.identifier}] Failed (attempt ${run.attempt}), retrying...`
587+
);
588+
run.attempt++;
589+
this.totalAttempts++;
590+
const backoffMs = Math.min(
591+
1000 * Math.pow(2, run.attempt - 1),
592+
300000
593+
);
594+
await new Promise((r) => setTimeout(r, backoffMs));
595+
} else {
596+
throw err;
595597
}
596-
} else {
597-
this.failCount++;
598-
console.log(`[${issue.identifier}] Failed: ${run.error}`);
599598
}
600-
} finally {
601-
this.running.delete(issueId);
602-
// Keep claimed so we don't re-dispatch within this session
603599
}
604600
}
605601

tsconfig.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@
2828
"declaration": true,
2929
"declarationMap": true,
3030
"sourceMap": true,
31-
"allowSyntheticDefaultImports": true
31+
"allowSyntheticDefaultImports": true,
32+
"incremental": true,
33+
"tsBuildInfoFile": ".tsbuildinfo"
3234
},
3335
"include": ["src/**/*", "scripts/**/*"],
3436
"exclude": [

0 commit comments

Comments
 (0)