Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 76 additions & 23 deletions .github/actions/cora-review/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -109,33 +109,86 @@ runs:
INPUT_BASE: ${{ inputs.base-branch }}
INPUT_SEVERITY: ${{ inputs.severity }}
run: |
# Use project .cora.yaml if it exists; otherwise create a CI-friendly fallback
if [ ! -f .cora.yaml ]; then
printf 'hook:\n max_diff_size: 5242880\n' > .cora-ci.yaml
CORA_CONFIG=".cora-ci.yaml" cora review \
--base "$INPUT_BASE" \
--format sarif \
--severity "$INPUT_SEVERITY" \
--quiet \
> cora-results.sarif 2>cora-stderr.log; EXIT_CODE=$?
else
echo "Using project .cora.yaml for configuration"
cora review \
--base "$INPUT_BASE" \
--format sarif \
--severity "$INPUT_SEVERITY" \
--quiet \
> cora-results.sarif 2>cora-stderr.log; EXIT_CODE=$?
fi
SARIF_BYTES=$(wc -c < cora-results.sarif)
echo "Cora review complete ($SARIF_BYTES bytes, exit=$EXIT_CODE)"
# Build the cora review command with retry logic and 600s timeout
run_cora() {
if [ ! -f .cora.yaml ]; then
# Create CI config with 5MB diff limit and 600s timeout
printf 'hook:\n max_diff_size: 5242880\nllm:\n timeout: 600\n' > .cora-ci.yaml
CORA_CONFIG=".cora-ci.yaml" cora review \
--base "$INPUT_BASE" \
--format sarif \
--severity "$INPUT_SEVERITY" \
--quiet \
> cora-results.sarif 2>cora-stderr.log
else
echo "Using project .cora.yaml for configuration"
cora review \
--base "$INPUT_BASE" \
--format sarif \
--severity "$INPUT_SEVERITY" \
--quiet \
> cora-results.sarif 2>cora-stderr.log
fi
return $?
}

# Retry up to 3 times with exponential backoff (10s, 30s, 60s)
EXIT_CODE=1
for ATTEMPT in 1 2 3; do
run_cora
EXIT_CODE=$?
SARIF_BYTES=$(wc -c < cora-results.sarif 2>/dev/null || echo "0")

echo "Attempt $ATTEMPT: exit=$EXIT_CODE, sarif_bytes=$SARIF_BYTES"

# Success: cora exited 0 or produced valid SARIF output
if [ "$EXIT_CODE" -eq 0 ] || [ "$SARIF_BYTES" -gt 10 ]; then
break
fi

# On failure, show stderr and retry
if [ -s cora-stderr.log ]; then
echo "::warning::Attempt $ATTEMPT failed. Cora stderr: $(cat cora-stderr.log)"
fi

if [ "$ATTEMPT" -lt 3 ]; then
SLEEP=$((ATTEMPT * 20))
echo "Retrying in ${SLEEP}s..."
sleep "$SLEEP"
rm -f cora-results.sarif cora-stderr.log
fi
done

echo "Cora review complete ($SARIF_BYTES bytes, exit=$EXIT_CODE, attempts=$ATTEMPT)"
if [ -s cora-stderr.log ]; then
echo "::warning::Cora stderr: $(cat cora-stderr.log)"
fi
# Fail if cora exited non-zero AND produced empty result (LLM API failure)

# All retries exhausted and still no valid output — generate a fallback SARIF
# so the workflow doesn't hard-fail on transient API issues.
if [ "$EXIT_CODE" -ne 0 ] && [ "$SARIF_BYTES" -lt 10 ]; then
echo "::error::Cora review failed (exit=$EXIT_CODE, $SARIF_BYTES bytes). LLM API may be unavailable."
exit 1
echo "::warning::Cora review could not complete after $ATTEMPT attempt(s). Generating fallback SARIF."
cat > cora-results.sarif << 'SARIF_EOF'
{
"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json",
"version": "2.1.0",
"runs": [{
"tool": {
"driver": {
"name": "cora",
"informationUri": "https://github.com/codecoradev/cora-cli",
"version": "0.4.0"
}
},
"results": [{
"level": "warning",
"message": {
"text": "Cora AI review could not complete — LLM API was unavailable after 3 retry attempts. Review was skipped; no code quality issues were found by the automated check."
}
}]
}]
}
SARIF_EOF
fi

- name: Check SARIF for findings
Expand Down
28 changes: 28 additions & 0 deletions src/commands/review.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,34 @@ pub async fn execute_review(
if progress.is_enabled() {
progress.error(&e.to_string(), "calling_llm");
}
// In SARIF format mode, produce a warning result instead of crashing.
// This ensures CI always gets valid SARIF even when the LLM API is
// temporarily unavailable or the diff is too large for the model.
if format == OutputFormat::Sarif {
let warning_output = serde_json::json!({
"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json",
"version": "2.1.0",
"runs": [{
"tool": {
"driver": {
"name": "cora",
"informationUri": "https://github.com/codecoradev/cora-cli",
"version": env!("CARGO_PKG_VERSION")
}
},
"results": [{
"level": "warning",
"message": {
"text": format!("Cora AI review could not complete: {}. Review was skipped; no code quality issues were found by the automated check.", e)
}
}]
}]
});
return Ok(ReviewResult {
exit_code: EXIT_OK,
output: format!("{warning_output}\n"),
});
}
return Err(e.into());
}
};
Expand Down
6 changes: 3 additions & 3 deletions src/config/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ impl Default for Config {
scan_system_prompt_file: None,
temperature: 0.0,
max_tokens: 4096,
timeout: 120,
timeout: 600,
cache_ttl: 1440, // 24h in minutes
static_analysis: StaticAnalysisConfig::default(),
rules_config: RulesConfig::default(),
Expand Down Expand Up @@ -860,7 +860,7 @@ scan:
#[test]
fn config_default_timeout() {
let cfg = Config::default();
assert_eq!(cfg.timeout, 120);
assert_eq!(cfg.timeout, 600);
}

#[test]
Expand Down Expand Up @@ -916,7 +916,7 @@ llm:
assert_eq!(cfg.temperature, 0.7);
// Other LLM fields should remain at defaults
assert_eq!(cfg.max_tokens, 4096);
assert_eq!(cfg.timeout, 120);
assert_eq!(cfg.timeout, 600);
assert_eq!(cfg.cache_ttl, 1440);
}

Expand Down
6 changes: 2 additions & 4 deletions src/engine/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ mod tests {
assert_eq!(cfg.provider, "openai");
assert_eq!(cfg.temperature, 0.0);
assert_eq!(cfg.max_tokens, 4096);
assert_eq!(cfg.timeout, 120);
assert_eq!(cfg.timeout, 600);
}

// ─── TokenUsage::default ───
Expand Down Expand Up @@ -435,14 +435,12 @@ impl Default for LLMConfig {
provider: "openai".to_string(),
temperature: 0.0,
max_tokens: 4096,
timeout: 120,
timeout: 600,
}
}
}

/// CLI exit codes.
///
/// Kept for API completeness — consumers and tests may reference these.
#[allow(dead_code)]
pub const EXIT_OK: i32 = 0;
#[allow(dead_code)]
Expand Down
Loading