Skip to content

[duplicate-code] Duplicate Code: Engine harness retry and failure-handling state machines #52380

Description

@github-actions
🔍 Duplicate Code Detected: Engine harness retry/state-machine flow

Analysis of commit cccc09ad0907430c5307578c485c51f1877dab95

Assignee: @copilot

Summary

The latest commit extends actions/setup/js/claude_harness.cjs with new cold-start connection-refused handling, but the surrounding retry orchestration is already duplicated across the Claude, Codex, and Copilot harnesses. Each file carries its own long retry/state machine for noop preflight, exponential backoff, failure telemetry, permission-denied handling, non-retryable guard handling, and retry exhaustion.

Duplication Details

Pattern: Shared retry orchestration duplicated across engine harnesses

  • Severity: Medium
  • Occurrences: 3
  • Locations:
    • actions/setup/js/claude_harness.cjs (lines 435-705)
    • actions/setup/js/codex_harness.cjs (lines 486-760)
    • actions/setup/js/copilot_harness.cjs (lines 1045-1399)
  • Code Sample:
if (safeOutputsPath && hasNoopInSafeOutputs(safeOutputsPath, { logger: log })) {
  log("pre-flight: noop message found in safe-outputs — skipping agent...");
  process.exit(0);
}

for (let attempt = 0; attempt <= maxRetries; attempt++) {
  if (attempt > 0) {
    log(`retry ${attempt}/${maxRetries}: sleeping ${delay}ms before next attempt ...`);
    await sleep(delay);
    delay = Math.min(delay * backoffMultiplier, maxDelayMs);
  }

  const result = await runProcess(...);
  if (result.exitCode === 0) {
    log(`success on attempt ${attempt + 1}...`);
    break;
  }

  const permissionDeniedCount = countPermissionDeniedIssues(result.output);
  const hasNumerousPermissionDenied = hasNumerousPermissionDeniedIssues(result.output);

  if (safeOutputsPath && hasNoopInSafeOutputs(safeOutputsPath, { logger: log })) {
    log(`attempt ${attempt + 1}: noop message found in safe-outputs — not retrying ...`);
    break;
  }

  const nonRetryableGuard = detectNonRetryableHarnessGuard(result.output);
  if (hasNumerousPermissionDenied) {
    ...
  }
}

Impact Analysis

  • Maintainability: Retry-policy fixes must be reimplemented in three places, which is already happening in practice. This commit added sessionHasProgress and isConnectionRefusedError only to the Claude harness, increasing policy drift risk.
  • Bug Risk: Shared behaviors such as noop short-circuiting, permission-denied suppression, AI-credit guard handling, and backoff logging can silently diverge between engines.
  • Code Bloat: The duplicated orchestration spans hundreds of lines across the three harnesses, while the engine-specific differences are mostly error classification and retry-mode selection.

Refactoring Recommendations

  1. Extract a shared harness retry runner

    • Extract common flow to: actions/setup/js/harness_retry_runner.cjs
    • Estimated effort: Medium
    • Benefits: Centralizes noop handling, backoff, guard checks, success/failure logging, and retry exhaustion behavior.
  2. Move engine-specific behavior behind callbacks

    • Extract per-engine hooks for: error classification, --continue eligibility, startup-only retries, and provider-specific preflight/setup.
    • Estimated effort: Medium
    • Benefits: Preserves engine-specific policy while removing copy-paste control flow.

Implementation Checklist

  • Review duplication findings
  • Define the shared retry-runner API
  • Extract common noop/backoff/guard branches
  • Move engine-specific retry policy into callbacks
  • Update harness tests to target shared and engine-specific behavior separately
  • Verify Claude/Codex/Copilot retry behavior remains unchanged

Analysis Metadata

  • Analyzed Files: 1 changed file (actions/setup/js/claude_harness.cjs), cross-referenced against 2 related harnesses
  • Detection Method: Serena semantic code analysis plus targeted line inspection
  • Commit: cccc09ad0907430c5307578c485c51f1877dab95
  • Analysis Date: 2026-08-12 UTC

Generated by 🔍 Duplicate Code Detector · gpt54 · 57.1 AIC · ⊞ 12.8K ·

  • expires on Aug 14, 2026, 2:10 PM UTC-08:00

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions