Skip to content

Warn on deprecated needs.activation.outputs.* with gh aw fix hint and track in warning count#43964

Closed
pelikhan with Copilot wants to merge 4 commits into
mainfrom
copilot/hippo-warn-on-deprecated-outputs
Closed

Warn on deprecated needs.activation.outputs.* with gh aw fix hint and track in warning count#43964
pelikhan with Copilot wants to merge 4 commits into
mainfrom
copilot/hippo-warn-on-deprecated-outputs

Conversation

Copilot AI commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Authors continue using deprecated needs.activation.outputs.{text,title,body} expressions because the compiler silently rewrites them — the existing warning lacked actionability and wasn't reflected in the compile warning summary.

Changes

  • Stronger diagnostic: Warning message now includes the exact gh aw fix migration hint:

    ⚠ Deprecated expression ${{ needs.activation.outputs.text }}: use ${{ steps.sanitized.outputs.text }} instead. Run `gh aw fix` to automatically update this workflow.
    
  • Warning count propagation (expression_extraction.go, compiler_yaml.go): Added deprecationWarningCount to ExpressionExtractor (exposed via GetDeprecationWarningCount()). All four ExtractExpressions call sites in the compiler now propagate this count to c.IncrementWarningCount() so deprecation warnings appear in compile summary statistics.

  • Tests (expression_extraction_test.go): New TestExpressionExtractor_DeprecationWarning covering: single/all-three deprecated expressions emit warnings; duplicates emit only one; non-deprecated activation outputs and unrelated expressions emit nothing; warning text includes both the exact deprecated form and replacement.

Copilot AI and others added 2 commits July 7, 2026 08:03
…tion and count tracking

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Add warning for deprecated needs.activation.outputs in workflow markdown Warn on deprecated needs.activation.outputs.* with gh aw fix hint and track in warning count Jul 7, 2026
Copilot AI requested a review from pelikhan July 7, 2026 08:05
@pelikhan pelikhan marked this pull request as ready for review July 7, 2026 08:20
Copilot AI review requested due to automatic review settings July 7, 2026 08:20
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

Copilot AI left a comment

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.

Pull request overview

Improves the workflow compiler’s diagnostics for deprecated needs.activation.outputs.{text,title,body} expressions by making the warning actionable (includes a gh aw fix hint) and ensuring these deprecations contribute to the compiler’s warning summary count.

Changes:

  • Expanded the deprecation warning message to include the exact replacement expression and a gh aw fix migration hint.
  • Added deprecation-warning counting to ExpressionExtractor and propagated that count into the compiler warning counter during prompt generation.
  • Added tests that validate warning emission/counting behavior and warning text content.
Show a summary per file
File Description
pkg/workflow/expression_extraction.go Tracks deprecated activation-output rewrites, emits stronger warnings, and exposes a deprecation warning count.
pkg/workflow/expression_extraction_test.go Adds test coverage for deprecation warning emission, deduping, and message content.
pkg/workflow/compiler_yaml.go Propagates extractor deprecation warning counts into the compiler’s warning summary and updates extraction helper signature.

Review details

Tip

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

  • Files reviewed: 3/3 changed files
  • Comments generated: 2
  • Review effort level: Low

Comment on lines +579 to +595
// Capture stderr to check for warnings
oldStderr := os.Stderr
r, w, pipeErr := os.Pipe()
if pipeErr != nil {
t.Fatalf("os.Pipe() error = %v", pipeErr)
}
os.Stderr = w

extractor := NewExpressionExtractor()
_, err := extractor.ExtractExpressions(tt.markdown)

w.Close()
os.Stderr = oldStderr
var buf bytes.Buffer
if _, copyErr := io.Copy(&buf, r); copyErr != nil {
t.Fatalf("io.Copy() error = %v", copyErr)
}
Comment on lines 1116 to +1119
// processMarkdownBody applies the standard post-processing pipeline to a markdown body:
// XML comment removal, expression wrapping, expression extraction/substitution, and chunking.
// It returns the prompt chunks and expression mappings extracted from the content.
func extractPromptChunksFromMarkdown(body string) ([]string, []*ExpressionMapping) {
// It returns the prompt chunks, expression mappings, and the number of deprecation warnings
// emitted for deprecated activation-output expressions.
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 90/100 — Excellent

Analyzed 1 test: 1 design, 0 implementation, 0 violation(s).

📊 Metrics (1 test)
Metric Value
Analyzed 1 (Go: 1, JS: 0)
✅ Design 1 (100%)
⚠️ Implementation 0 (0%)
Edge/error coverage 1 (100%)
Duplicate clusters 0
Inflation YES (11.4:1)
🚨 Violations 0
Test File Classification Issues
TestExpressionExtractor_DeprecationWarning pkg/workflow/expression_extraction_test.go:517 behavioral_contract, design_test, high_value Test inflation flagged (11.4:1 ratio)
⚠️ Flagged Tests (1)

TestExpressionExtractor_DeprecationWarning (pkg/workflow/expression_extraction_test.go:517) — Table-driven test covering 6 scenarios with strong edge-case coverage (deduplication, negative cases, boundary conditions). The 11.4:1 inflation ratio is justified: comprehensive behavioral testing of deprecation warnings, message content, warning counts, and deduplication logic. All assertions are meaningful and verify user-facing behavior. No false comfort detected.

Verdict

Passed. 0% implementation tests (threshold: 30%). Test inflation flagged but justified—comprehensive table-driven coverage of complex deprecation-tracking feature with no mock violations or build-tag issues.

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • awmgmcpg

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "awmgmcpg"

See Network Configuration for more information.

🧪 Test quality analysis by Test Quality Sentinel · 26.6 AIC · ⌖ 11.4 AIC · ⊞ 6.8K ·
Comment /review to run again

@github-actions github-actions Bot left a comment

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.

✅ Test Quality Sentinel: 90/100. 0% implementation tests (threshold: 30%). Table-driven test with comprehensive edge-case coverage (deduplication, negative scenarios, message content verification). No mock violations or build-tag issues detected.

…ng propagation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

🏗️ Design Decision Gate — ADR Required

This PR makes significant changes to core business logic (145 new lines in pkg/) but does not have a linked Architecture Decision Record (ADR).

📄 Draft ADR committed: docs/adr/43964-deprecated-activation-output-warning-propagation.md — review and complete it before merging.

🔒 This PR cannot merge until an ADR is linked in the PR body.

📋 What to do next
  1. Review the draft ADR committed to your branch — it was generated from the PR diff
  2. Complete the missing sections — add context the AI could not infer, refine the decision rationale, and list real alternatives you considered
  3. Commit the finalized ADR to docs/adr/ on your branch
  4. Reference the ADR in this PR body by adding a line such as:

    ADR: ADR-43964: Track and Propagate Deprecated Activation-Output Warning Counts to Compile Summary

Once an ADR is linked in the PR body, this gate will re-run and verify the implementation matches the decision.

Why ADRs Matter

"AI made me procrastinate on key design decisions. Because refactoring was cheap, I could always say 'I'll deal with this later.' Deferring decisions corroded my ability to think clearly."

ADRs create a searchable, permanent record of why the codebase looks the way it does. Future contributors (and your future self) will thank you.

📋 Michael Nygard ADR Format Reference

An ADR must contain these four sections to be considered complete:

  • Context — What is the problem? What forces are at play?
  • Decision — What did you decide? Why?
  • Alternatives Considered — What else could have been done?
  • Consequences — What are the trade-offs (positive and negative)?

All ADRs are stored in docs/adr/ as Markdown files numbered by PR number (e.g., 0042-use-postgresql.md for PR #42).

🏗️ ADR gate enforced by Design Decision Gate 🏗️ · 43.8 AIC · ⌖ 10 AIC · ⊞ 8.5K ·
Comment /review to run again

@github-actions github-actions Bot left a comment

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.

Review summary

The PR is well-scoped and does exactly what it advertises: improves the deprecation warning message with the gh aw fix migration hint and propagates the deprecation count into the compiler's warning summary. The logic is correct, and deduplication (via the existing mappings map keyed on originalExpr) prevents double-counting.

Three non-blocking issues noted:

  1. Whitespace edge case in deduplication (expression_extraction.go line 135) — two tokens for the same deprecated expression that differ only in whitespace produce different originalExpr keys, so each emits its own warning and increments the counter. Probably not reachable in practice (the regex normalizes whitespace), but worth a comment.

  2. for range N { IncrementWarningCount() } verbosity (compiler_yaml.go, repeated 4×) — this is functionally equivalent to a single warningCount += N. Adding an AddWarningCount(n int) helper to Compiler would clean this up.

  3. os.Stderr global mutation in tests (expression_extraction_test.go line 580) — swapping the global os.Stderr with a pipe is not goroutine-safe under -race or if t.Parallel() is added later. Injecting an io.Writer into ExpressionExtractor (constructor option or functional option) would let tests pass a bytes.Buffer directly.

All three are suggestions/improvements, not blockers. The feature logic and test coverage are solid.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · 67.9 AIC · ⌖ 5.4 AIC · ⊞ 4.8K

fmt.Sprintf("Deprecated expression ${{ %s }}: use ${{ %s }} instead.", originalContent, content),
fmt.Sprintf("Deprecated expression ${{ %s }}: use ${{ %s }} instead. Run `gh aw fix` to automatically update this workflow.", originalContent, content),
))
e.deprecationWarningCount++

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.

Minor: deduplication relies on originalExpr string equality — whitespace variants could slip through

The early-return guard at line 126 uses originalExpr (the full ${{ ... }} token string) as the deduplication key. The deprecation counter is only incremented for unseen expressions, which is correct.

However, if two tokens for the same logical expression differ in internal whitespace (e.g. ${{ needs.activation.outputs.text }} vs ${{needs.activation.outputs.text }}), they produce different originalExpr keys, each triggering its own warning and counter increment even though they refer to the same deprecated output. A brief comment here noting that deduplication is keyed on the raw token (not the transformed content) would clarify the assumption.

@copilot please address this.

userPromptChunks = append(userPromptChunks, chunks...)
expressionMappings = append(expressionMappings, exprMaps...)
for range deprecationCount {
c.IncrementWarningCount()

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.

Style: for range N { c.IncrementWarningCount() } can be simplified to c.AddWarningCount(deprecationCount) if that helper exists, or c.warningCount += deprecationCount

This pattern is repeated 4 times in this file. A single c.IncrementWarningCount() call increments by 1; calling it inside a loop to add N is equivalent to c.warningCount += deprecationCount. If Compiler ever gets an AddWarningCount(n int) helper, or if warningCount is directly accessible, this becomes cleaner. As-is it's correct but verbose — consider extracting a helper or using a single arithmetic increment.

@copilot please address this.

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Capture stderr to check for warnings
oldStderr := os.Stderr

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.

Reliability concern: swapping os.Stderr in tests is not goroutine-safe

os.Stderr is a process-global variable. Replacing it with os.Pipe() in a test and restoring it with defer (or manually) will race if the test suite ever runs with -parallel or if t.Parallel() is added later. Any concurrent test or background goroutine that writes to os.Stderr during this window will either be captured in the pipe buffer or write to the wrong descriptor.

Consider injecting a io.Writer for the warning output into ExpressionExtractor (e.g. WithStderr(w io.Writer) or a constructor option), so tests can pass a bytes.Buffer directly without mutating global state.

@copilot please address this.

@github-actions github-actions Bot left a comment

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.

REQUEST_CHANGES — 3 blocking issues, 1 non-blocking

Summary: The feature logic is correct and the diagnostic improvement is valuable. Three issues need addressing before merge: a test-reliability bug from missing defer on os.Stderr restoration, stale deprecation count propagated when extraction errors, and a structural design issue (hardcoded os.Stderr) that the new test inadvertently exposes by requiring an os.Pipe workaround.

🔍 Findings detail

Blocking

  1. os.Stderr not restored via defer in tests (expression_extraction_test.go:585): Any panic or future t.Fatal inside the test body (between assignment and restore) leaves os.Stderr pointing at a closed pipe for the rest of the test binary. The fix is a t.Cleanup call immediately after os.Stderr = w. See inline comment.

  2. Deprecation count propagated on extraction error (compiler_yaml.go:1130): If ExtractExpressions fails mid-parse, exprMappings is discarded but GetDeprecationWarningCount() still returns the partial count, which gets added to the compiler's warning summary. Return 0 on the error path. See inline comment.

  3. os.Stderr hardcoded in ExpressionExtractor (expression_extraction.go:132): Forces the os.Pipe global-mutation hack in tests, making tests fragile and the type non-reusable outside a CLI. An injectable io.Writer field (defaulting to os.Stderr) would fix both the production design and the test issue simultaneously. See inline comment.

Non-blocking (should fix)

  1. for range N { IncrementWarningCount() } is O(N) arithmetic (compiler_yaml.go:535, same pattern 6×): Add AddWarningCount(n int) to Compiler. See inline comment.

🔎 Code quality review by PR Code Quality Reviewer · 94.8 AIC · ⌖ 8.04 AIC · ⊞ 5.4K
Comment /review to run again

if pipeErr != nil {
t.Fatalf("os.Pipe() error = %v", pipeErr)
}
os.Stderr = w

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.

os.Stderr is not restored via defer — a panic inside ExtractExpressions permanently corrupts the global stderr handle for the rest of the test binary.

💡 Suggested fix

Replace the inline restore pattern with t.Cleanup so cleanup is guaranteed even on panic or t.Fatal:

oldStderr := os.Stderr
r, w, pipeErr := os.Pipe()
if pipeErr != nil {
    t.Fatalf("os.Pipe() error = %v", pipeErr)
}
os.Stderr = w
t.Cleanup(func() {
    w.Close()
    os.Stderr = oldStderr
})

extractor := NewExpressionExtractor()
_, err := extractor.ExtractExpressions(tt.markdown)

The current pattern (os.Stderr = w; <work>; w.Close(); os.Stderr = oldStderr) is not panic-safe. If ExtractExpressions ever panics or a future contributor adds a t.Fatal between the assignment and the restore, os.Stderr is left pointing at a closed write-end pipe. Every subsequent test in the binary then writes into a broken pipe — output is silently lost and the file descriptor leaks. Using t.Cleanup is the standard Go idiom to prevent this.

exprMappings = nil
}
return splitContentIntoChunks(body), exprMappings
return splitContentIntoChunks(body), exprMappings, extractor.GetDeprecationWarningCount()

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.

Deprecation warning count is propagated even when ExtractExpressions fails mid-parse, double-counting warnings against a compile run that encountered an error.

💡 Suggested fix
func extractPromptChunksFromMarkdown(body string) ([]string, []*ExpressionMapping, int) {
    body = removeXMLComments(body)
    body = wrapExpressionsInTemplateConditionals(body)
    extractor := NewExpressionExtractor()
    exprMappings, err := extractor.ExtractExpressions(body)
    if err != nil {
        exprMappings = nil
        // Do not propagate deprecation count from a failed extraction;
        // stderr warnings may have been partially emitted, but the count
        // should not inflate the compiler's summary.
        return splitContentIntoChunks(body), nil, 0
    }
    if len(exprMappings) == 0 {
        exprMappings = nil
    }
    return splitContentIntoChunks(body), exprMappings, extractor.GetDeprecationWarningCount()
}

Currently, if ExtractExpressions returns an error (expressions were partially processed), exprMappings is discarded, but GetDeprecationWarningCount() still returns however many warnings were emitted before the error. The caller increments the compiler's warning count for those, and the warning messages have already been printed to stderr, so they appear in output but may refer to expressions that weren't fully handled. The correct behavior on extraction failure is to return 0 deprecation warnings so the summary counter stays consistent with the actual compilation state.

chunks, exprMaps, deprecationCount := extractPromptChunksFromMarkdown(cleaned)
userPromptChunks = append(userPromptChunks, chunks...)
expressionMappings = append(expressionMappings, exprMaps...)
for range deprecationCount {

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.

for range deprecationCount is used as arithmetic — an O(N) loop calling IncrementWarningCount() N times when a single addition would do. This pattern is duplicated 6 times in the diff.

💡 Suggested fix

Add an AddWarningCount(n int) helper to Compiler and use it:

// in compiler_types.go
func (c *Compiler) AddWarningCount(n int) {
    c.warningCount += n
}

Then replace all six occurrences:

// before
for range deprecationCount {
    c.IncrementWarningCount()
}

// after
c.AddWarningCount(deprecationCount)

The for range <int> idiom (Go 1.22+) is readable for iteration, but using it to accumulate a counter inverts its semantics — it looks like each iteration does meaningful work when it doesn't. With 6 identical patterns in the same function, this warrants a helper. If a workflow triggers 1000 deprecation warnings, the current code spins 6000 no-op iterations just to compute a sum.

@@ -122,8 +130,9 @@ func (e *ExpressionExtractor) processMatch(originalExpr, rawContent string) {
// Emit deprecation warning once per unique deprecated activation-output expression
if content != originalContent && strings.HasPrefix(content, "steps.sanitized.outputs.") {
fmt.Fprintln(os.Stderr, console.FormatWarningMessage(

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.

ExpressionExtractor writes directly to os.Stderr — a hidden side-effect that makes the type untestable without an os.Pipe hack and unusable in non-CLI contexts.

💡 Suggested fix

Inject a writer via the extractor or return warnings as values:

type ExpressionExtractor struct {
    mappings                map[string]*ExpressionMapping
    counter                 int
    deprecationWarningCount int
    stderr                  io.Writer // injected; defaults to os.Stderr
}

func NewExpressionExtractor() *ExpressionExtractor {
    return &ExpressionExtractor{
        mappings: make(map[string]*ExpressionMapping),
        stderr:   os.Stderr,
    }
}

Then in processMatch:

fmt.Fprintln(e.stderr, console.FormatWarningMessage(...))

In tests, inject bytes.NewBuffer(nil) instead of using os.Pipe. This removes the need to mutate a process-global variable in tests, eliminates the leak risk entirely, and makes the type usable in library contexts (Wasm, embedded, server) where writing to os.Stderr is inappropriate. The new deprecationWarningCount field already exposes the count — adding an injectable writer completes the decoupling.

@github-actions github-actions Bot left a comment

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.

Skills-Based Review 🧠

Applied /tdd, /diagnosing-bugs, and /codebase-design — requesting changes on three targeted issues.

📋 Key Themes & Highlights

Issues to Address

  1. os.Stderr global mutation in tests (expression_extraction_test.go:585) — not goroutine-safe; will race under -race; restore is also not panic-safe (no defer). Ideal fix: inject an io.Writer into ExpressionExtractor.

  2. for range N { IncrementWarningCount() } repeated 6 times (compiler_yaml.go) — adding an AddWarningCount(n int) helper would replace these loops with a single, intent-revealing call each.

  3. Deprecation predicate infers from transformed output rather than the original key (expression_extraction.go:131) — future transformers that also emit steps.sanitized.outputs.* would silently trigger false deprecation warnings. Checking against the known set of deprecated originals is safer.

Positive Highlights

  • ✅ Excellent test coverage: 6 cases covering deduplication, non-deprecated cases, and exact replacement text matching.
  • GetDeprecationWarningCount() accessor is clean; count propagation to the compiler summary is correctly wired at all call sites.
  • ✅ Warning message is now genuinely actionable — the gh aw fix hint is exactly what users need.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · 84.8 AIC · ⌖ 8.23 AIC · ⊞ 6.6K
Comment /matt to run again

if pipeErr != nil {
t.Fatalf("os.Pipe() error = %v", pipeErr)
}
os.Stderr = w

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.

[/tdd] os.Stderr is a global; mutating it in a subtest is not goroutine-safe and will race if the test suite (or any sibling tests) ever runs with -race or t.Parallel().

💡 Safer approach: redirect at the consumer, not the global

Instead of swapping os.Stderr, thread an io.Writer through ExpressionExtractor (e.g. extractor.SetOutput(w)) or use console.SetOutput. That keeps tests hermetic and eliminates the data race.

If a writer field is not available today, at minimum add a defer func() { os.Stderr = oldStderr }() guard so the restore is panic-safe:

oldStderr := os.Stderr
r, w, pipeErr := os.Pipe()
if pipeErr != nil {
    t.Fatalf("os.Pipe() error = %v", pipeErr)
}
os.Stderr = w
defer func() { os.Stderr = oldStderr }()

@copilot please address this.

chunks, exprMaps, deprecationCount := extractPromptChunksFromMarkdown(cleaned)
userPromptChunks = append(userPromptChunks, chunks...)
expressionMappings = append(expressionMappings, exprMaps...)
for range deprecationCount {

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.

[/codebase-design] for range deprecationCount { c.IncrementWarningCount() } loops N times to add N to a counter — this is semantically c.AddWarningCount(n) but there is no such method. This pattern appears 6 times and obscures intent.

💡 Add an `IncrementWarningCountBy(n int)` or `AddWarningCount(n int)` helper

In compiler_types.go, alongside the existing IncrementWarningCount:

func (c *Compiler) AddWarningCount(n int) {
    c.warningCount += n
}

Then all six call sites become a single clear line:

c.AddWarningCount(deprecationCount)

This is shorter, communicates the intent, and avoids the for range int Go 1.22+ idiom which surprises readers unfamiliar with that version.

@copilot please address this.

@@ -122,8 +130,9 @@ func (e *ExpressionExtractor) processMatch(originalExpr, rawContent string) {
// Emit deprecation warning once per unique deprecated activation-output expression
if content != originalContent && strings.HasPrefix(content, "steps.sanitized.outputs.") {

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.

[/diagnosing-bugs] The deprecation check fires only when content != originalContent && strings.HasPrefix(content, "steps.sanitized.outputs."). If a future transformer also produces a steps.sanitized.outputs.* value via a different path, it would silently get counted as a deprecation warning — a false positive with no test coverage guarding against it.

💡 Guard against false positives with a dedicated predicate

Introduce an explicit function that checks whether the original content (before any transformation) is a known deprecated activation output, rather than inferring it from the transformed result:

func isDeprecatedActivationOutput(original string) bool {
    switch original {
    case "needs.activation.outputs.text",
         "needs.activation.outputs.title",
         "needs.activation.outputs.body":
        return true
    }
    return false
}

Then in processMatch:

if isDeprecatedActivationOutput(originalContent) {
    // emit warning and increment count
}

This makes the intent explicit and ensures new transformers do not accidentally trigger deprecation warnings.

@copilot please address this.

},
},
{
name: "duplicate deprecated expression emits only one warning",

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.

[/tdd] The "duplicate deprecated expression" test case has no wantWarningPhrases — it only asserts the count is 1 but does not verify that one warning was actually emitted to stderr. Without asserting the warning text, a silent counter-only increment would pass this test.

💡 Add a stderr phrase assertion to the duplicate case
{
    name:                 "duplicate deprecated expression emits only one warning",
    markdown:             `${{ needs.activation.outputs.text }} ${{ needs.activation.outputs.text }}`,
    wantDeprecationCount: 1,
    wantWarningPhrases: []string{
        "needs.activation.outputs.text",
        "gh aw fix",
    },
},

This ensures the count and the side-effect (stderr output) are both tested together.

@copilot please address this.

@pelikhan pelikhan closed this Jul 7, 2026
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.

[hippo] Warn on deprecated needs.activation.outputs.* in workflow markdown

3 participants