Skip to content

Fix safe-inputs script: runner always returning {"stdout":"","stderr":""}#19493

Merged
pelikhan merged 8 commits intomainfrom
copilot/fix-safe-inputs-script-error
Mar 4, 2026
Merged

Fix safe-inputs script: runner always returning {"stdout":"","stderr":""}#19493
pelikhan merged 8 commits intomainfrom
copilot/fix-safe-inputs-script-error

Conversation

Copy link
Contributor

Copilot AI commented Mar 4, 2026

  • Understand the bug: generateSafeInputJavaScriptToolScript generates a .cjs that only exports execute() but never calls it when run as subprocess
  • Fix generateSafeInputJavaScriptToolScript in pkg/workflow/safe_inputs_generator.go to add a require.main === module block that reads stdin, calls execute(), and writes JSON to stdout
  • Refactor main execution block to use async/await instead of Promise chaining
  • Move the main execution harness to actions/setup/js/safe-inputs-runner.cjs; generated scripts delegate via require('./safe-inputs-runner.cjs')(execute)
  • Add actions/setup/js/safe-inputs-runner.test.cjs with 4 Vitest tests (happy path, empty stdin, JSON parse error, execute error)
  • Update actions/setup/setup.sh to copy safe-inputs-runner.cjs to the safe-inputs directory
  • Update test in safe_inputs_generator_test.go to assert require('./safe-inputs-runner.cjs') delegation
  • Fix TS18046: catch (e) variable is unknown; use e instanceof Error ? e.message : String(e) guard
  • All tests pass: make fmt, TypeScript check, Vitest tests
Original prompt

This section details on the original issue you should resolve

<issue_title>Bug: safe-inputs with script: always returns {"stdout":"","stderr":""}</issue_title>
<issue_description># Bug: safe-inputs with script: always returns {"stdout":"","stderr":""}

Summary

A safe-inputs entry using the script: runner always produces {"stdout":"","stderr":""} when called, regardless of what the script does. The return value is never surfaced to the agent.

Steps to Reproduce

Define a safe-input with script::

safe-inputs:
  github-graphql:
    inputs:
      query:
        type: string
        required: true
      variables:
        type: string
        required: false
    env:
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    script: |
      const graphql = require('@octokit/graphql').graphql.defaults({
        headers: { authorization: `token ${process.env.GITHUB_TOKEN}` }
      });
      const vars = inputs.variables ? JSON.parse(inputs.variables) : {};
      const result = await graphql(inputs.query, vars);
      return result;

Call it from a workflow. The agent receives:

{"stdout":"","stderr":""}

What I Tried

  • Using return result (object)
  • Using console.log(JSON.stringify(result)) — no output in stdout
  • Simplified scripts (e.g. return { viewer: { login: "test" } }) — still empty
  • The same logic works fine with py: using print(json.dumps(...)) — the Python runner correctly captures stdout

Expected Behavior

The script: runner should:

  1. Capture the return value and serialize it as the tool output as documented

Actual Behavior

Every script: safe-input returns {"stdout":"","stderr":""} unconditionally. The agent sees no data and has to assume defaults / skip logic that depended on the result.

Impact

Workflows that rely on script: for GitHub GraphQL queries (or any JS-based safe-input) silently fail. The only workaround is to rewrite the logic in Python using py: or use run: with shell commands, which is significantly less ergonomic for GraphQL/JSON workloads.

Environment

  • gh aw (github/gh-aw) — current version in use
  • Runner: GitHub Actions (AWF sandbox)
    </issue_description>

Comments on the Issue (you are @copilot in this section)


🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.



✨ PR Review Safe Output Test - Run 22656026286

💥 [THE END] — Illustrated by Smoke Claude ·

… main execution block to generated .cjs files

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix safe-inputs to correctly return stdout and stderr Fix safe-inputs script: runner always returning {"stdout":"","stderr":""} Mar 4, 2026
@pelikhan
Copy link
Contributor

pelikhan commented Mar 4, 2026

@copilot use async await

…ion block

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 4, 2026

@copilot use async await

Done in db05438. The stdin.on("end", ...) callback is now async and uses await execute(inputs) with a try/catch instead of Promise chaining.

@pelikhan
Copy link
Contributor

pelikhan commented Mar 4, 2026

@copilot move generated code to a separate file that gets required (safe-inputs-runner.cjs). Add tests , update setup.sh

…ests, update setup.sh

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 4, 2026

@copilot move generated code to a separate file that gets required (safe-inputs-runner.cjs). Add tests , update setup.sh

Done in b25a2c2. The main execution harness is now in actions/setup/js/safe-inputs-runner.cjs. Generated scripts just do require('./safe-inputs-runner.cjs')(execute) instead of inlining the stdin/stdout boilerplate. Added safe-inputs-runner.test.cjs with 4 Vitest tests (happy path, empty stdin, JSON parse error, execute error), and added the file to SAFE_INPUTS_FILES in setup.sh.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 4, 2026

📰 BREAKING: Smoke Copilot is now investigating this pull request. Sources say the story is developing...

@github-actions
Copy link
Contributor

github-actions bot commented Mar 4, 2026

🎬 THE ENDSmoke Claude MISSION: ACCOMPLISHED! The hero saves the day! ✨

@github-actions
Copy link
Contributor

github-actions bot commented Mar 4, 2026

✅ All tools validated successfully! Agent Container Smoke Test confirms agent container is ready.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 4, 2026

🌑 The shadows whisper... Smoke Codex failed. The oracle requires further meditation...

@github-actions
Copy link
Contributor

github-actions bot commented Mar 4, 2026

Commit pushed: 9027415

Generated by Changeset Generator

@github-actions
Copy link
Contributor

github-actions bot commented Mar 4, 2026

Agent Container Tool Check

Tool Status Version
bash 5.2.21
sh available
git 2.53.0
jq 1.7
yq 4.52.4
curl 8.5.0
gh 2.87.3
node 20.20.0
python3 3.12.3
go 1.24.13
java available (proxy settings detected)
dotnet 10.0.102

Result: 12/12 tools available ✅

Overall Status: PASS

🔧 Tool validation by Agent Container Smoke Test ·

@github-actions
Copy link
Contributor

github-actions bot commented Mar 4, 2026

Smoke test by @Copilot ✅/❌:

  • ✅ GitHub MCP, Safe Inputs GH CLI, Playwright, Web Fetch, File Write, Bash, Discussion, Build, Discussion Create, Workflow Dispatch, PR Review
  • ❌ Serena MCP (tools not available)

Overall: ⚠️ PARTIAL PASS — cc @pelikhan @Copilot

📰 BREAKING: Report filed by Smoke Copilot ·

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Fix looks correct — the root cause (generated scripts never writing to stdout) is properly addressed by extracting the runner into a shared module. The require.main === module guard is the right pattern. Two minor suggestions: add a timeout guard in the runner, and enhance the generated comment to explain the stdin/stdout protocol.

📰 BREAKING: Report filed by Smoke Copilot

try {
const result = await execute(inputs);
process.stdout.write(JSON.stringify(result));
} catch (err) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Consider adding a timeout to the stdin read to prevent hanging if the parent process closes the pipe unexpectedly. Something like process.stdin.resume() with a watchdog timer could guard against zombie processes.

sb.WriteString("// Run when executed directly (as a subprocess by the MCP handler)\n")
sb.WriteString("if (require.main === module) {\n")
sb.WriteString(" require(\"./safe-inputs-runner.cjs\")(execute);\n")
sb.WriteString("}\n")
Copy link
Contributor

Choose a reason for hiding this comment

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

The generated comment // Run when executed directly (as a subprocess by the MCP handler) is helpful. Consider also noting why this pattern is needed (the MCP handler spawns the script as a subprocess and communicates via stdin/stdout), which would make the generated code self-documenting for anyone reading it.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 4, 2026

📰 VERDICT: Smoke Copilot has concluded. All systems operational. This is a developing story. 🎤

@github-actions
Copy link
Contributor

github-actions bot commented Mar 4, 2026

Smoke Test Results - Run §22656026286

Core tests (#1–10): ⚠️ ✅ ❌ ✅ ✅ ✅ ✅ ✅ ✅ ✅
PR review tests (#11–17): ✅ ✅ ✅ ⚠️ ✅ ✅ ⚠️

Overall: PARTIAL

💥 [THE END] — Illustrated by Smoke Claude ·

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

💥 Automated smoke test review - all systems nominal! The safe-inputs runner refactor looks clean — shared module pattern reduces duplication and the tests cover all edge cases.

💥 [THE END] — Illustrated by Smoke Claude

@pelikhan
Copy link
Contributor

pelikhan commented Mar 4, 2026

@github-actions
Copy link
Contributor

github-actions bot commented Mar 4, 2026

Hey @Copilot 👋 — great work tracking down this bug in the safe-inputs script: runner! The root cause (the generated .cjs file never invoking execute as a subprocess entry point) is clearly identified, and the fix is clean and well-structured.

This PR looks well-aligned with the project's contribution guidelines and code quality standards. Here's a quick summary of what looks great:

  • Focused — single, coherent bug fix across exactly the right files
  • Well described — detailed PR body with before/after code, runner logic, and link to the originating issue (Bug: safe-inputs with script: always returns {"stdout":"","stderr":""} #19489)
  • Tests included — both Vitest tests for the new safe-inputs-runner.cjs and updated Go assertions in safe_inputs_generator_test.go
  • Changeset added.changeset/patch-safe-inputs-runner.md is present
  • setup.sh updatedsafe-inputs-runner.cjs correctly added to SAFE_INPUTS_FILES
  • Process followed — community issue filed by @srgibbs99, picked up by a core team member (@pelikhan) and implemented via the Copilot coding agent

The PR is currently in draft state — once any remaining CI checks pass and the draft is marked ready, this looks well-positioned for maintainer review. 🚀

Generated by Contribution Check ·

…puts-runner.cjs

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 4, 2026

@copilot fix test

CI / js (pull_request)Failing after 23s
...

Fixed in 6116f5c. The TypeScript error TS18046: 'e' is of type 'unknown' was caused by accessing .message directly on a catch variable. Changed to e instanceof Error ? e.message : String(e).

@pelikhan pelikhan marked this pull request as ready for review March 4, 2026 05:28
Copilot AI review requested due to automatic review settings March 4, 2026 05:28
Copy link
Contributor

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

Fixes safe-inputs JavaScript script: tools always returning empty {"stdout":"","stderr":""} by ensuring generated scripts actually execute when run as a subprocess, via a shared runner harness.

Changes:

  • Update JS safe-input tool script generation to include a require.main === module entrypoint that delegates subprocess execution to a shared runner.
  • Add actions/setup/js/safe-inputs-runner.cjs and Vitest coverage for stdin parsing, parse errors, and execute errors.
  • Update setup packaging to copy the new runner into /opt/gh-aw/safe-inputs and add a changeset entry.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.

Show a summary per file
File Description
pkg/workflow/safe_inputs_generator.go Generated JS safe-input scripts now delegate subprocess execution to safe-inputs-runner.cjs.
pkg/workflow/safe_inputs_generator_test.go Extends generator test to assert the new subprocess entrypoint + delegation.
actions/setup/js/safe-inputs-runner.cjs New shared Node subprocess harness: reads stdin JSON, calls execute, writes JSON to stdout.
actions/setup/js/safe-inputs-runner.test.cjs New Vitest coverage for the runner’s stdin parsing and error paths.
actions/setup/setup.sh Ensures safe-inputs-runner.cjs is copied into the safe-inputs runtime directory.
.changeset/patch-safe-inputs-runner.md Declares a patch release note for the safe-inputs runner fix.
Comments suppressed due to low confidence (2)

actions/setup/js/safe-inputs-runner.cjs:37

  • After JSON.parse, inputs can be a non-object (null/array/primitive) if a caller sends unexpected JSON. The generated scripts/JSDoc assume an object, and downstream destructuring/logic may behave incorrectly. Consider normalizing parsed values so only plain objects are passed to execute (e.g., if parsed value is not a non-array object, fall back to {}).
    let inputs = {};
    try {
      if (inputJson.trim()) {
        inputs = JSON.parse(inputJson.trim());
      }

actions/setup/js/safe-inputs-runner.cjs:47

  • On execute failure, the runner writes String(err) and immediately calls process.exit(1). String(err) can produce unhelpful output (e.g., [object Object]) and omits stack traces for Error objects, and an immediate process.exit can truncate pending stderr writes. Consider formatting errors similarly to the rest of the codebase (message + optional stack, newline-terminated) and setting process.exitCode = 1 (or exiting in the write callback) to ensure the error output is flushed before termination.
    } catch (err) {
      process.stderr.write(String(err));
      process.exit(1);
    }

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

You can also share your feedback on Copilot code review. Take the survey.

@pelikhan pelikhan merged commit 567fee9 into main Mar 4, 2026
44 checks passed
@pelikhan pelikhan deleted the copilot/fix-safe-inputs-script-error branch March 4, 2026 05:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: safe-inputs with script: always returns {"stdout":"","stderr":""}

3 participants