Skip to content

fix: validate artifact content before marking workflow step done#1098

Open
mvanhorn wants to merge 2 commits into
Fission-AI:mainfrom
mvanhorn:fix/1084-openspec-artifact-content-validation
Open

fix: validate artifact content before marking workflow step done#1098
mvanhorn wants to merge 2 commits into
Fission-AI:mainfrom
mvanhorn:fix/1084-openspec-artifact-content-validation

Conversation

@mvanhorn
Copy link
Copy Markdown

@mvanhorn mvanhorn commented May 15, 2026

Summary

Workflow interruptions (network blip, Ctrl-C, agent timeout) leave partially-written artifact files behind. On resume, detectCompleted sees proposal.md exists and marks the artifact done, so the workflow skips re-generating it. The half-written file then flows into downstream tasks, degrading every subsequent artifact silently.

Add a content-validation layer in artifact-graph/outputs.ts:

  • artifactOutputContentValid(changeDir, generates) — every resolved output file has at least one non-blank, non-HTML-comment line
  • artifactOutputComplete(changeDir, generates) — composes artifactOutputExists with content validation

Switch isArtifactComplete in state.ts to use artifactOutputComplete. Keep artifactOutputExists and resolveArtifactOutputs semantics unchanged so other callers (apply instructions, glob resolution) aren't affected.

Why this matters

Reporter @jiehu03 in #1084 identified the root cause precisely: detectCompleted -> isArtifactComplete -> artifactOutputExists only check file existence (fs.statSync / fast-glob). An empty file and a complete file produce identical "done" verdicts; there's no warning, no diff, no error — only a half-written artifact silently flowing into the next step.

Tests: 7 new cases under test/core/artifact-graph/ cover empty, whitespace-only, comment-only, valid content, and glob-pattern scenarios. pnpm vitest run test/core/artifact-graph/ -> 145 passed.

Fixes #1084

AI was used for assistance.

Summary by CodeRabbit

  • Bug Fixes
    • Workflow resume now regenerates artifact files that are empty, whitespace-only, or contain only comments instead of treating them as complete.
    • Artifact completion now requires meaningful content in generated files; glob-generated artifacts are marked incomplete if any matched file lacks meaningful content.

Review Change Stack

@mvanhorn mvanhorn requested a review from TabishB as a code owner May 15, 2026 23:53
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 15, 2026

📝 Walkthrough

Walkthrough

This PR changes artifact completion detection to require generated outputs contain meaningful content (not empty, whitespace-only, or comment-only), ensuring workflow resumes regenerate incomplete artifact files instead of treating them as completed.

Changes

Artifact Content Validation for Workflow Resume

Layer / File(s) Summary
Output content validation helpers
src/core/artifact-graph/outputs.ts
New exported validators artifactOutputContentValid and artifactOutputComplete check that generated outputs exist and all contain at least one meaningful non-empty line not starting with <!--. Internal helper isArtifactOutputFileContentValid reads files as UTF-8 and rejects empty, whitespace-only, or unreadable outputs.
Completion detection integration
src/core/artifact-graph/state.ts
detectCompleted and isArtifactComplete now use artifactOutputComplete instead of artifactOutputExists, changing completion from "file exists" to "file exists and contains content." JSDoc and internal helper are updated.
Output validation test coverage
test/core/artifact-graph/outputs.test.ts
New test cases validate behavior for empty files, whitespace-only content, meaningful heading lines, and glob patterns where any resolved file is empty. Tests confirm empty files fail validation while files with non-comment content pass.
Completion detection integration tests
test/core/artifact-graph/state.test.ts
New test cases verify detectCompleted marks artifacts incomplete when generated files are empty or whitespace-only, and complete when they contain meaningful lines like headings. Glob-pattern tests confirm the glob is not complete if any matched file is empty.
Release notes
.changeset/validate-artifact-content.md
Documents patch-level bugfix for @fission-ai/openspec: workflow resumes now regenerate empty or whitespace-only artifact files instead of treating them as completed.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • Fission-AI/OpenSpec#967: Builds on the shared artifact-output resolver by switching detectCompleted and isArtifactComplete from artifactOutputExists to the new content-validity validators.
  • Fission-AI/OpenSpec#971: Changes resolveArtifactOutputs() canonicalization which the new content validators rely upon for reading resolved file paths.

Suggested reviewers

  • TabishB
  • alfred-openspec

Poem

🐰 Blank files hid in the grass,
I sniffed them out with a careful pass.
Now content must sparkle, not merely exist,
Resume hops forward — no artifact missed. 🎉

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Title directly and clearly describes the main change: adding content validation to artifact completion checks before marking workflow steps done.
Linked Issues check ✅ Passed All code objectives from #1084 are met: content validation functions added to outputs.ts, detectCompleted switched to use artifactOutputComplete in state.ts, and comprehensive tests verify empty, whitespace-only, comment-only file handling.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the linked issue: new validation functions, modified completion checks, updated tests, and changeset documentation. No unrelated modifications detected.
Docstring Coverage ✅ Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.changeset/validate-artifact-content.md:
- Line 7: Update the changeset note sentence so it explicitly mentions
"comment-only" artifact files in addition to empty or whitespace-only files;
change the phrasing in the existing summary line (the sentence that currently
reads "Fixed workflow resumes so empty or whitespace-only artifact files are
regenerated instead of being treated as completed.") to include a short clause
like "and files containing only comments (e.g., HTML comment-only files)" so the
note matches the actual behavior.

In `@src/core/artifact-graph/outputs.ts`:
- Around line 56-59: artifactOutputComplete currently triggers
resolveArtifactOutputs twice via artifactOutputExists and
artifactOutputContentValid; change it to call resolveArtifactOutputs once, store
the resolved outputs, and use those results to perform the existence and
content-validity checks. Either refactor artifactOutputExists and
artifactOutputContentValid to accept the pre-resolved outputs (e.g., add
parameters like resolvedOutputs) or implement the existence/content checks
directly inside artifactOutputComplete using resolveArtifactOutputs' return;
reference artifactOutputComplete, artifactOutputExists,
artifactOutputContentValid, and resolveArtifactOutputs when updating code so you
only resolve once and reuse the resolved list for both checks.

In `@test/core/artifact-graph/outputs.test.ts`:
- Around line 46-58: Add a new unit test in outputs.test.ts that writes
HTML-comment-only content into the same temp file used by the other cases (e.g.,
write only an HTML comment like <!-- note --> to proposal.md in tempDir) and
assert that artifactOutputContentValid(tempDir, 'proposal.md') === false and
artifactOutputComplete(tempDir, 'proposal.md') === false; place this test
alongside the existing whitespace-only and heading tests so the helper-level
contract for comment-only content is explicitly covered.

In `@test/core/artifact-graph/state.test.ts`:
- Around line 90-101: Add a negative test case to ensure files that contain only
HTML comments are not treated as completed: in
test/core/artifact-graph/state.test.ts add an it(...) similar to the existing
heading test that uses createSchema and ArtifactGraph.fromSchema to create the
graph, writes a file at path.join(tempDir, 'proposal.md') containing only HTML
comments (e.g. "<!-- comment -->\n") via fs.writeFileSync, calls
detectCompleted(graph, tempDir), and asserts
expect(completed.has('proposal')).toBe(false); this mirrors the existing
single-heading test but verifies the "non-comment meaningful content" rule.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f1d893d1-6079-4df1-ba92-cf0de9c45737

📥 Commits

Reviewing files that changed from the base of the PR and between 8498042 and dca846e.

📒 Files selected for processing (5)
  • .changeset/validate-artifact-content.md
  • src/core/artifact-graph/outputs.ts
  • src/core/artifact-graph/state.ts
  • test/core/artifact-graph/outputs.test.ts
  • test/core/artifact-graph/state.test.ts

Comment thread .changeset/validate-artifact-content.md Outdated
Comment thread src/core/artifact-graph/outputs.ts
Comment thread test/core/artifact-graph/outputs.test.ts
Comment thread test/core/artifact-graph/state.test.ts
…tests

- artifactOutputComplete now resolves outputs once instead of twice
- changeset note mentions comment-only files (matches impl behavior)
- outputs.test.ts: comment-only artifact rejected by content validator
- state.test.ts: detectCompleted excludes comment-only files

Tests: pnpm vitest run test/core/artifact-graph/ — 147 passed.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/core/artifact-graph/outputs.ts`:
- Around line 67-73: The current check uses trimmed.startsWith('<!--') so
multi-line HTML comments like <!--\nfoo\n--> cause the middle lines (e.g. "foo")
to be treated as content; update the logic that determines "meaningful" content
by either stripping all HTML comment blocks from content before splitting (use a
non-greedy block removal for <!-- ... -->) or by iterating lines with an
inComment boolean that flips when encountering <!-- and --> so lines inside a
comment are ignored; adjust the code that uses content.split(...).some(...) (and
the trimmed variable/startsWith('<!--') test) to use this new comment-aware
approach so only real non-comment, non-whitespace lines count as meaningful
content.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 985611bb-85fe-48b1-8c7e-dfddfc6cb0b2

📥 Commits

Reviewing files that changed from the base of the PR and between dca846e and ce0c1d7.

📒 Files selected for processing (4)
  • .changeset/validate-artifact-content.md
  • src/core/artifact-graph/outputs.ts
  • test/core/artifact-graph/outputs.test.ts
  • test/core/artifact-graph/state.test.ts
✅ Files skipped from review due to trivial changes (1)
  • .changeset/validate-artifact-content.md

Comment on lines +67 to +73
return content
.split(/\r?\n/)
.some((line) => {
const trimmed = line.trim();

return trimmed.length > 0 && !trimmed.startsWith('<!--');
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Multi-line HTML comment blocks are incorrectly treated as meaningful content.

On Line 67–73, comment detection only checks trimmed.startsWith('<!--'). For block comments like <!--\ncomment\n-->, the middle line (comment) is currently counted as valid content, so comment-only files can be marked complete.

💡 Proposed fix
 function isArtifactOutputFileContentValid(filePath: string): boolean {
   try {
     const content = fs.readFileSync(filePath, 'utf8');
-
-    return content
-      .split(/\r?\n/)
-      .some((line) => {
-        const trimmed = line.trim();
-
-        return trimmed.length > 0 && !trimmed.startsWith('<!--');
-      });
+    const withoutHtmlComments = content.replace(/<!--[\s\S]*?(-->|$)/g, '');
+    return withoutHtmlComments
+      .split(/\r?\n/)
+      .some((line) => line.trim().length > 0);
   } catch {
     return false;
   }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return content
.split(/\r?\n/)
.some((line) => {
const trimmed = line.trim();
return trimmed.length > 0 && !trimmed.startsWith('<!--');
});
function isArtifactOutputFileContentValid(filePath: string): boolean {
try {
const content = fs.readFileSync(filePath, 'utf8');
const withoutHtmlComments = content.replace(/<!--[\s\S]*?(-->|$)/g, '');
return withoutHtmlComments
.split(/\r?\n/)
.some((line) => line.trim().length > 0);
} catch {
return false;
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/artifact-graph/outputs.ts` around lines 67 - 73, The current check
uses trimmed.startsWith('<!--') so multi-line HTML comments like <!--\nfoo\n-->
cause the middle lines (e.g. "foo") to be treated as content; update the logic
that determines "meaningful" content by either stripping all HTML comment blocks
from content before splitting (use a non-greedy block removal for <!-- ... -->)
or by iterating lines with an inComment boolean that flips when encountering
<!-- and --> so lines inside a comment are ignored; adjust the code that uses
content.split(...).some(...) (and the trimmed variable/startsWith('<!--') test)
to use this new comment-aware approach so only real non-comment, non-whitespace
lines count as meaningful content.

@mvanhorn
Copy link
Copy Markdown
Author

Addressed in ce0c1d7:

  • outputs.ts:59 (Major) - artifactOutputComplete now resolves outputs once and reuses the result, dropping the duplicate resolveArtifactOutputs call through the existence path.
  • .changeset - note updated to mention comment-only files alongside empty/whitespace-only (matches what the validator actually filters).
  • outputs.test.ts - new case asserts artifactOutputContentValid and artifactOutputComplete both return false for a file containing only HTML comments.
  • state.test.ts - new detectCompleted case asserts a comment-only proposal.md is excluded from the completed set.

Tests: pnpm vitest run test/core/artifact-graph/ -- 147 passed.

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.

Incomplete artifacts marked as "done" after workflow interruption

1 participant