Skip to content

web: pipeline visibility on failure#72

Merged
PrzemyslawKlys merged 2 commits intomainfrom
codex/powerforge-pipeline-visibility
Feb 7, 2026
Merged

web: pipeline visibility on failure#72
PrzemyslawKlys merged 2 commits intomainfrom
codex/powerforge-pipeline-visibility

Conversation

@PrzemyslawKlys
Copy link
Member

Changes:\n- Always write pipeline profile to .powerforge/pipeline-profile.json on failure (configurable via profileOnFail, default true).\n- Audit: default �udit-summary.json + �udit.sarif written on failure only (new summaryOnFail/sarifOnFail, default true).\n- Optimize: default report written to .powerforge/optimize-report.json when any optimize action is enabled.\n\nWhy:\n- Makes �udit/optimize/pipeline failures actionable without reruns or huge console spam.

Copilot AI review requested due to automatic review settings February 7, 2026 20:46
@claude
Copy link

claude bot commented Feb 7, 2026

Code Review: Pipeline Visibility on Failure

Summary

This PR improves pipeline failure diagnostics by ensuring critical output files (profiles, audit summaries, and optimization reports) are written even when pipelines fail. This is a valuable improvement for debugging CI/CD failures.


✅ Strengths

  1. Clear Intent: The PR addresses a real pain point - making failures actionable without needing to rerun pipelines or parse console logs
  2. Good Defaults: New flags default to true (write on fail), which is the most useful behavior for debugging
  3. Consistent Naming: Uses both camelCase and kebab-case parameter variants for flexibility
  4. Backward Compatible: Changes are additive and don't break existing configurations

🔍 Code Quality Observations

1. Profile Writing Logic (WebPipelineRunner.cs:973, 1004)

Issue: Inconsistent conditions for writing profile on success vs failure

// On failure (line 973):
if (\!string.IsNullOrWhiteSpace(profilePath) && (profileEnabled || profileWriteOnFail))

// On success (line 1004):  
if (\!string.IsNullOrWhiteSpace(profilePath) && profileEnabled)

Analysis: The failure path writes when profileEnabled OR profileWriteOnFail, but success path only writes when profileEnabled. This creates an edge case:

  • If profile: false and profileOnFail: true (default), failures get a profile but successes don't
  • This seems intentional based on the PR description, but could be confusing

Recommendation: Add a code comment explaining this asymmetric behavior, or consider simplifying to always write if profilePath exists:

// Write profile on failure when profileOnFail is true (default)
// even if profile tracking is disabled, to aid debugging
if (\!string.IsNullOrWhiteSpace(profilePath) && (profileEnabled || profileWriteOnFail))

2. Audit Summary/SARIF Path Resolution (WebPipelineRunner.cs:617-622, 806-811)

Potential Issue: Default paths are set AFTER explicit path resolution

var resolvedSummaryPath = ResolveSummaryPathForPipeline(summary, summaryPath);
if (string.IsNullOrWhiteSpace(resolvedSummaryPath) && summaryOnFail)
    resolvedSummaryPath = ResolvePathWithinRoot(baseDir, null, Path.Combine(".powerforge", "audit-summary.json"));

Question: What does ResolveSummaryPathForPipeline return when summary=false and summaryPath is set? If it returns null/empty, the default path kicks in. If it returns the custom path, the default is ignored.

Recommendation: Verify this logic works as intended. Consider simplifying:

var resolvedSummaryPath = \!string.IsNullOrWhiteSpace(summaryPath)
    ? ResolveSummaryPathForPipeline(summary, summaryPath)
    : (summaryOnFail ? ResolvePathWithinRoot(baseDir, null, Path.Combine(".powerforge", "audit-summary.json")) : null);

3. Optimize Report Auto-Generation (WebPipelineRunner.cs:480-484)

Good Practice: Automatically creates a report when any optimization is enabled

if (string.IsNullOrWhiteSpace(reportPath) &&
    (minifyHtml || minifyCss || minifyJs || optimizeImages || hashAssets || cacheHeaders))
{
    reportPath = ResolvePathWithinRoot(baseDir, null, Path.Combine(".powerforge", "optimize-report.json"));
}

Note: This always writes the report (not just on failure), which differs from the audit behavior. This is reasonable since optimize steps typically succeed, and the report shows savings/metrics.

4. Audit Options - OnFailOnly Flags (WebSiteAuditor.cs:742-743, 781-782)

Correctness Check: The condition logic is correct

if (\!string.IsNullOrWhiteSpace(options.SummaryPath) &&
    (\!options.SummaryOnFailOnly || \!result.Success))

Logic Analysis:

  • Writes when path is set AND (not fail-only mode OR audit failed)
  • ✅ Correct: If SummaryOnFailOnly=false, always write. If SummaryOnFailOnly=true, only write when \!result.Success

🐛 Potential Issues

Issue 1: Edge Case in Audit Step Parameter Passing

In WebPipelineRunner.cs lines 831-838, the new parameters are set:

SummaryPath = resolvedSummaryPath,
SarifPath = resolvedSarifPath,
SummaryMaxIssues = summaryMax,
SummaryOnFailOnly = summaryOnFail && \!summary,
SarifOnFailOnly = sarifOnFail && \!sarif,

Concern: The logic summaryOnFail && \!summary means:

  • If summary=true and summaryOnFail=true, SummaryOnFailOnly becomes false
  • This forces always-write behavior when explicit summary is enabled

Is this intended? It seems like the intent is: "if user explicitly enabled summary, always write it; otherwise use the fail-only flag." This is reasonable but could be documented.

Issue 2: Duplicate Code

The audit step logic appears twice in the file (around lines 614-650 and 772-850) with nearly identical parameter parsing. This creates maintenance burden.

Recommendation: Extract common parameter parsing into a helper method to ensure consistency and reduce duplication.


⚡ Performance Considerations

  • File I/O: Additional file writes only occur on failure (or when explicitly enabled), so minimal performance impact
  • Path Resolution: Multiple calls to ResolvePathWithinRoot are lightweight
  • No blocking operations: All writes are synchronous but fast (JSON serialization)

No performance concerns


🔒 Security Considerations

  1. Path Traversal: All paths use ResolvePathWithinRoot which should prevent directory traversal attacks
  2. File Permissions: Written to .powerforge/ directory - ensure this directory has appropriate permissions in production
  3. Sensitive Data: Audit reports may contain URL patterns, file paths, and error messages - ensure these don't leak secrets

No major security concerns (assuming ResolvePathWithinRoot is properly implemented)


🧪 Test Coverage

Missing: No visible test changes in this PR

Recommendations:

  1. Add tests for profileOnFail behavior (success vs failure)
  2. Test summaryOnFail and sarifOnFail flags
  3. Verify default path generation for optimize reports
  4. Test edge cases: summary=true + summaryOnFail=true

📋 Documentation

Missing: No documentation updates visible

Recommendations:

  • Update pipeline configuration docs to mention new flags
  • Add examples showing when files are written
  • Document the .powerforge/ directory structure

Final Verdict

Overall: ✅ Approve with minor suggestions

This PR delivers valuable functionality for debugging pipeline failures. The code is generally well-structured and backward compatible. Main suggestions:

  1. Add clarifying comments for asymmetric profile write logic
  2. Consider extracting duplicate audit parameter parsing
  3. Add test coverage for new flags
  4. Update documentation

The changes are production-ready as-is, but addressing the suggestions would improve maintainability.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dd6fda5581

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +617 to +619
var resolvedSummaryPath = ResolveSummaryPathForPipeline(summary, summaryPath);
if (string.IsNullOrWhiteSpace(resolvedSummaryPath) && summaryOnFail)
resolvedSummaryPath = ResolvePathWithinRoot(baseDir, null, Path.Combine(".powerforge", "audit-summary.json"));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use resolved summary path in audit options

This branch computes resolvedSummaryPath with the new summaryOnFail fallback, but the audit call still uses ResolveSummaryPathForPipeline(summary, summaryPath) instead of the resolved value. With default summaryOnFail: true and summary: false, failed audits will not write the expected .powerforge/audit-summary.json, so the new failure-only summary artifact is silently skipped.

Useful? React with 👍 / 👎.

Comment on lines +621 to +623
var resolvedSarifPath = ResolveSarifPathForPipeline(sarif, sarifPath);
if (string.IsNullOrWhiteSpace(resolvedSarifPath) && sarifOnFail)
resolvedSarifPath = ResolvePathWithinRoot(baseDir, null, Path.Combine(".powerforge", "audit.sarif"));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Honor sarifOnFail in the audit step options

These lines assign a SARIF output path whenever sarifOnFail is true (the new default), but this audit branch does not set WebAuditOptions.SarifOnFailOnly. In WebSiteAuditor.Audit, SARIF is written whenever SarifPath is present unless SarifOnFailOnly is true, so successful audits will now produce SARIF files even though the pipeline option is intended to be failure-only.

Useful? React with 👍 / 👎.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR aims to improve post-failure visibility in the web pipeline by emitting profiling/audit/optimize artifacts automatically so failures are actionable without reruns.

Changes:

  • Add SummaryOnFailOnly / SarifOnFailOnly switches to WebAuditOptions and gate summary/SARIF writing on audit failure.
  • Extend pipeline config handling with profileOnFail, summaryOnFail, and sarifOnFail defaults.
  • Add a default optimize report path when any optimize action is enabled.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 7 comments.

File Description
PowerForge.Web/Services/WebSiteAuditor.cs Adds “write outputs only on failure” switches for audit summary/SARIF.
PowerForge.Web.Cli/WebPipelineRunner.cs Adds pipeline defaults for profile-on-fail, audit artifacts on fail, and optimize report output.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

: null;
var profileEnabled = (GetBool(root, "profile") ?? false) || forceProfile;
var profileWriteOnFail = GetBool(root, "profileOnFail") ?? GetBool(root, "profile-on-fail") ?? true;
var profilePath = ResolvePathWithinRoot(baseDir, GetString(root, "profilePath") ?? GetString(root, "profile-path"), Path.Combine(".powerforge", "pipeline-profile.json"));
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

profilePath is resolved/validated unconditionally. If a config provides an out-of-root profilePath but both profile and profileOnFail are false, the pipeline will now throw during startup even though profiling is disabled. Consider only calling ResolvePathWithinRoot when (profileEnabled || profileWriteOnFail) (or deferring resolution until you know you will write a profile).

Suggested change
var profilePath = ResolvePathWithinRoot(baseDir, GetString(root, "profilePath") ?? GetString(root, "profile-path"), Path.Combine(".powerforge", "pipeline-profile.json"));
var profilePathSetting = GetString(root, "profilePath") ?? GetString(root, "profile-path");
var profilePath = (profileEnabled || profileWriteOnFail)
? ResolvePathWithinRoot(baseDir, profilePathSetting, Path.Combine(".powerforge", "pipeline-profile.json"))
: profilePathSetting ?? Path.Combine(".powerforge", "pipeline-profile.json");

Copilot uses AI. Check for mistakes.
if (string.IsNullOrWhiteSpace(reportPath) &&
(minifyHtml || minifyCss || minifyJs || optimizeImages || hashAssets || cacheHeaders))
{
reportPath = ResolvePathWithinRoot(baseDir, null, Path.Combine(".powerforge", "optimize-report.json"));
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default reportPath is resolved to an absolute path under the pipeline root, but WebAssetOptimizerOptions.ReportPath is treated as a path relative to SiteRoot (see TryResolveUnderRoot). Passing an absolute pipeline path will fail resolution and the report won’t be written (only a trace warning). Either pass a relative path under siteRoot (e.g. .powerforge/optimize-report.json) or extend the optimizer to support an explicit absolute report path.

Suggested change
reportPath = ResolvePathWithinRoot(baseDir, null, Path.Combine(".powerforge", "optimize-report.json"));
// Use a path relative to the site root so WebAssetOptimizerOptions.ReportPath is resolved correctly.
reportPath = Path.Combine(".powerforge", "optimize-report.json");

Copilot uses AI. Check for mistakes.
var resolvedSarifPath = ResolveSarifPathForPipeline(sarif, sarifPath);
var resolvedSummaryPath = ResolveSummaryPathForPipeline(summary, summaryPath);
if (string.IsNullOrWhiteSpace(resolvedSummaryPath) && summaryOnFail)
resolvedSummaryPath = ResolvePathWithinRoot(baseDir, null, Path.Combine(".powerforge", "audit-summary.json"));
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolvedSummaryPath is computed (and may be defaulted to .powerforge/audit-summary.json when summaryOnFail is true) but it is not used when building WebAuditOptions later in this case block, so the new “summary on fail” behavior won’t take effect. Use resolvedSummaryPath for SummaryPath and wire SummaryOnFailOnly so the file is only written on failures.

Suggested change
resolvedSummaryPath = ResolvePathWithinRoot(baseDir, null, Path.Combine(".powerforge", "audit-summary.json"));
resolvedSummaryPath = ResolvePathWithinRoot(baseDir, null, Path.Combine(".powerforge", "audit-summary.json"));
if (!string.IsNullOrWhiteSpace(resolvedSummaryPath))
summaryPath = resolvedSummaryPath;

Copilot uses AI. Check for mistakes.
Comment on lines +621 to +623
var resolvedSarifPath = ResolveSarifPathForPipeline(sarif, sarifPath);
if (string.IsNullOrWhiteSpace(resolvedSarifPath) && sarifOnFail)
resolvedSarifPath = ResolvePathWithinRoot(baseDir, null, Path.Combine(".powerforge", "audit.sarif"));
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolvedSarifPath is set to an absolute path under the pipeline root (.powerforge/audit.sarif). WebSiteAuditor requires SarifPath to resolve under SiteRoot and will throw when writing. Prefer a path relative to siteRoot (e.g. .powerforge/audit.sarif) or adjust the auditor’s path validation if pipeline-root outputs are intended, and set SarifOnFailOnly so sarifOnFail=true doesn’t write on success.

Copilot uses AI. Check for mistakes.
Comment on lines +808 to +812
resolvedSummaryPath = ResolvePathWithinRoot(baseDir, null, Path.Combine(".powerforge", "audit-summary.json"));

var resolvedSarifPath = ResolveSarifPathForPipeline(sarif, sarifPath);
if (string.IsNullOrWhiteSpace(resolvedSarifPath) && sarifOnFail)
resolvedSarifPath = ResolvePathWithinRoot(baseDir, null, Path.Combine(".powerforge", "audit.sarif"));
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In doctor, the default resolvedSummaryPath is resolved to an absolute path under the pipeline root via ResolvePathWithinRoot. Since WebSiteAuditor enforces output paths under SiteRoot (effectiveSiteRoot here), this will throw when trying to write the summary. Use a path relative to effectiveSiteRoot (e.g. .powerforge/audit-summary.json) or relax the auditor’s validation if pipeline-root outputs are required.

Suggested change
resolvedSummaryPath = ResolvePathWithinRoot(baseDir, null, Path.Combine(".powerforge", "audit-summary.json"));
var resolvedSarifPath = ResolveSarifPathForPipeline(sarif, sarifPath);
if (string.IsNullOrWhiteSpace(resolvedSarifPath) && sarifOnFail)
resolvedSarifPath = ResolvePathWithinRoot(baseDir, null, Path.Combine(".powerforge", "audit.sarif"));
resolvedSummaryPath = Path.Combine(".powerforge", "audit-summary.json");
var resolvedSarifPath = ResolveSarifPathForPipeline(sarif, sarifPath);
if (string.IsNullOrWhiteSpace(resolvedSarifPath) && sarifOnFail)
resolvedSarifPath = Path.Combine(".powerforge", "audit.sarif");

Copilot uses AI. Check for mistakes.

var resolvedSarifPath = ResolveSarifPathForPipeline(sarif, sarifPath);
if (string.IsNullOrWhiteSpace(resolvedSarifPath) && sarifOnFail)
resolvedSarifPath = ResolvePathWithinRoot(baseDir, null, Path.Combine(".powerforge", "audit.sarif"));
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, resolvedSarifPath is resolved under the pipeline root, but WebSiteAuditor requires SARIF output paths to be under effectiveSiteRoot and will throw when writing. Use a path relative to effectiveSiteRoot (e.g. .powerforge/audit.sarif) or adjust path validation to support pipeline-root outputs.

Suggested change
resolvedSarifPath = ResolvePathWithinRoot(baseDir, null, Path.Combine(".powerforge", "audit.sarif"));
resolvedSarifPath = ResolvePathWithinRoot(effectiveSiteRoot!, null, Path.Combine(".powerforge", "audit.sarif"));

Copilot uses AI. Check for mistakes.
Comment on lines +1004 to +1008
if (!string.IsNullOrWhiteSpace(profilePath) && profileEnabled)
{
WritePipelineProfile(profilePath, result, logger);
result.ProfilePath = profilePath;
}
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the non-exception completion path, the profile is only written when profileEnabled is true. This misses failures where no exception is thrown but result.Success ends up false (e.g. unknown task). To implement “write profile on failure”, include the failure condition here as well (e.g. profileEnabled || (profileWriteOnFail && !result.Success)).

Copilot uses AI. Check for mistakes.
@claude
Copy link

claude bot commented Feb 7, 2026

Pull Request Review: Pipeline Visibility on Failure

Overview

This PR improves failure debugging in PowerForge pipelines by automatically writing diagnostic artifacts (profiles, audit summaries, SARIF reports, optimize reports) when pipeline steps fail, without requiring reruns or verbose console output.


✅ Strengths

1. Smart Default Behavior

The asymmetric profile-writing logic is well-designed:

  • Success path: Only writes profile when explicitly enabled (avoids overhead)
  • Failure path: Writes profile when enabled OR profileOnFail=true (default), making CI failures immediately actionable

This is clearly documented in the comment at WebPipelineRunner.cs:973-976 and follows the principle of "fail loudly, succeed quietly."

2. Consistent Naming Conventions

Supports both camelCase and kebab-case for all new options:

  • profileOnFail / profile-on-fail
  • summaryOnFail / summary-on-fail
  • sarifOnFail / sarif-on-fail

This maintains consistency with the existing codebase pattern.

3. Well-Structured Conditional Logic

The SummaryOnFailOnly and SarifOnFailOnly flags use clean boolean logic:

SummaryOnFailOnly = summaryOnFail && !summary,
SarifOnFailOnly = sarifOnFail && !sarif,

This correctly ensures that:

  • If summary=true, always write (existing behavior preserved)
  • If summary=false and summaryOnFail=true, write only on failure

4. Sensible Auto-Paths

When optimize actions are enabled without an explicit report path, a default .powerforge/optimize-report.json is created (WebPipelineRunner.cs:481-484). This ensures failures leave actionable artifacts without requiring verbose configuration.


🔍 Issues & Recommendations

1. Critical: Logic Bug in Profile Writing (Success Path)

Location: WebPipelineRunner.cs:1008

Issue: On the success path, the profile is only written when profileEnabled is true:

if (!string.IsNullOrWhiteSpace(profilePath) && profileEnabled)

However, profilePath is now always set (line 74), regardless of whether profiling is enabled:

var profilePath = ResolvePathWithinRoot(baseDir, GetString(root, "profilePath") ?? GetString(root, "profile-path"), Path.Combine(".powerforge", "pipeline-profile.json"));

Before this PR: profilePath was null when profiling was disabled, so the success-path write logic worked correctly.

After this PR: profilePath is never null, so the condition !string.IsNullOrWhiteSpace(profilePath) is always true.

Problem: This means profiles will never be written on success, even when profile=true is explicitly set, breaking existing functionality.

Recommendation:
The success-path condition should match the failure-path logic pattern. Either:

  1. Make profilePath nullable again and set it conditionally, OR
  2. Change line 1008 to check both conditions like the failure path does

Suggested fix:

// Line 1008 - Make consistent with failure path
if (!string.IsNullOrWhiteSpace(profilePath) && (profileEnabled || profileWriteOnFail))

Wait, actually looking more carefully - the success path should ONLY write when profileEnabled is true (to avoid overhead). The current code is correct in intent but may have a subtle issue.

Let me reconsider: The failure path writes when (profileEnabled || profileWriteOnFail) because we want artifacts on failure even if profiling wasn't explicitly enabled. The success path should only write when profileEnabled is true. So line 1008 appears correct in logic.

However, there's still a concern: if profile=false and profileOnFail=true (the defaults), the profile path is still computed but never written on success. This is fine - it's just a small waste of computation. But it's not a bug.

Actually, I need to trace through the logic more carefully:

Scenario 1: profile=false (default), profileOnFail=true (default)

  • profileEnabled = false
  • profileWriteOnFail = true
  • profilePath = ".powerforge/pipeline-profile.json"
  • On success: Condition is false && true = false ✅ No profile written (correct)
  • On failure: Condition is true && (false || true) = true ✅ Profile written (correct)

Scenario 2: profile=true, profileOnFail=true

  • profileEnabled = true
  • profileWriteOnFail = true
  • profilePath = ".powerforge/pipeline-profile.json"
  • On success: Condition is true && true = true ✅ Profile written (correct)
  • On failure: Condition is true && (true || true) = true ✅ Profile written (correct)

Okay, the logic is actually correct! My initial concern was unfounded. The success path should only check profileEnabled, not profileWriteOnFail.

2. Minor: Inconsistent Indentation

Location: Multiple locations in the diff

Several sections show indentation changes that appear to be whitespace-only. For example, lines 443-539 in the optimize section and lines 542-648 in the audit section show re-indentation.

Impact: While this doesn't affect functionality, it makes the diff harder to review and could cause merge conflicts.

Recommendation: If this is just an IDE auto-format issue, consider:

  • Separating pure formatting changes into a separate commit, OR
  • Configuring IDE to avoid auto-reformatting untouched code

3. Potential: Race Condition in File Writing

Location: WebSiteAuditor.cs:742-778, WebPipelineRunner.cs:973-981

Observation: Multiple file writes happen during exception handling without explicit error handling around I/O operations.

Scenario: If profile/summary/SARIF writing fails (disk full, permissions, network path unavailable), the exception would:

  1. Mask the original pipeline failure reason
  2. Potentially lose important diagnostic information

Recommendation: Consider wrapping file writes in try-catch blocks and logging warnings if they fail, rather than allowing I/O exceptions to propagate:

try
{
    WritePipelineProfile(profilePath, result, logger);
    result.ProfilePath = profilePath;
}
catch (Exception ex)
{
    logger?.Warning($"Failed to write profile to {profilePath}: {ex.Message}");
}

This ensures the original failure reason is preserved even if artifact writing fails.

4. Documentation Gap

The PR description mentions the new flags but doesn't specify:

  • What happens when both summary=true and summaryOnFail=true are set (answer: summary is always written)
  • Whether profilePath is resolved even when profiling is disabled (answer: yes, minor overhead)

Recommendation: Add these details to the PR description or inline code comments for future maintainers.


🔒 Security Considerations

Path Traversal Protection: The code uses ResolvePathWithinRoot for all default paths, which should prevent path traversal attacks. ✅

File Overwrites: Profile/summary/SARIF files are written without checking if they exist. This is likely intentional (latest run should overwrite), but confirm this is desired behavior when multiple pipelines run concurrently.


⚡ Performance Considerations

Minimal Impact:

  • profilePath is now always computed (even when profile=false), but this is just string manipulation - negligible overhead
  • File writing only happens on failure or when explicitly enabled, so no performance regression on happy path

🧪 Test Coverage

Observation: This PR doesn't include test updates.

Recommendation: Consider adding tests for:

  1. Profile written on failure even when profile=false but profileOnFail=true
  2. Summary/SARIF written on failure when summaryOnFail/sarifOnFail are true
  3. Summary/SARIF always written when explicitly enabled, regardless of *OnFail flags
  4. Optimize report auto-generated when optimize actions are enabled

Existing test file PowerForge.Tests/ModulePipelineCleanupOnFailureTests.cs suggests there's already infrastructure for testing failure scenarios.


📝 Summary

Overall: This is a well-thought-out enhancement that addresses a real pain point in CI debugging. The implementation is clean and follows existing patterns.

Critical Issues: 1 logic bug in success-path profile writing Actually, after careful analysis, the logic is correct!

Recommendations:

  1. Fix the profile writing logic on the success path (Not needed - logic is correct)
  2. Consider adding defensive error handling around file I/O during failure scenarios
  3. Add test coverage for the new failure-path behaviors
  4. Consider separating whitespace changes if possible

Approval Status: Once the profile writing bug is fixed and tests are added, this PR will be ready to merge. The core functionality is sound and provides significant value for debugging pipeline failures.


Review completed by Claude Code

@PrzemyslawKlys PrzemyslawKlys merged commit 267044a into main Feb 7, 2026
1 check passed
@PrzemyslawKlys PrzemyslawKlys deleted the codex/powerforge-pipeline-visibility branch February 7, 2026 21:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants