Skip to content

Zero-argument custom safe-output tools show help instead of calling MCP #49301

Description

@tobio

Problem statement

The MCP CLI bridge currently prevents valid zero-argument custom safe-output tools from being called. Every safeoutputs invocation whose parsed arguments are {} is treated as a schema-discovery probe, so the bridge prints per-tool help and returns before issuing MCP tools/call.

This is a runtime regression: the compiler intentionally supports custom safe-output jobs with no inputs and generates an empty object schema for them.

{
  "type": "object",
  "properties": {},
  "additionalProperties": false
}

User impact and use case

Downstream repository: https://github.com/elastic/terraform-provider-elasticstack

The affected workflow uses a custom safe-output tool named dispatch_code_factory. Calling the tool records a safe-output item; a later dispatch_code_factory GitHub Actions job checks for that output type before dispatching code-factory runs for issues created by the agent.

Because the bridge exits after showing help, no item is recorded. The later job's if: condition evaluates false and silently skips the intended dispatches.

Evidence:

The downstream repository is adding a required dummy boolean input as a temporary mitigation. That avoids {} but does not resolve the general runtime contract violation.

Reproduction

Given a generated tool named dispatch_code_factory with the empty schema above, both supported zero-argument forms fail:

$ safeoutputs dispatch_code_factory
[warning] [safeoutputs] No arguments provided for 'dispatch_code_factory'; showing command help instead of calling the tool

$ printf '{}' | safeoutputs dispatch_code_factory .
[warning] [safeoutputs] No arguments provided for 'dispatch_code_factory'; showing command help instead of calling the tool

Expected behavior

Both invocations should proceed through MCP initialization and issue:

{
  "method": "tools/call",
  "params": {
    "name": "dispatch_code_factory",
    "arguments": {}
  }
}

The safe-output item should then be recorded and the downstream custom job should be eligible to run.

Actual behavior

The bridge invokes showToolHelp, returns with a successful exit, and never sends tools/call.

Agent analysis and execution trace

The failure is in actions/setup/js/mcp_cli_bridge.cjs, not in custom-job schema generation or the MCP server:

  1. main() loads the cached tool definitions.
  2. It resolves matchedTool, including matchedTool.inputSchema.
  3. parseToolArgs correctly produces {} for both a bare invocation and piped {} with the . sentinel.
  4. shouldShowToolHelpForEmptyArgs(serverName, toolArgs) currently ignores matchedTool and returns true for every safeoutputs call with {}.
  5. main() displays help and returns before mcpInitialize, mcpNotifyInitialized, and mcpToolsCall.

Current guard:

function shouldShowToolHelpForEmptyArgs(serverName, toolArgs) {
  return serverName === SAFEOUTPUTS_SERVER_NAME && Object.keys(toolArgs).length === 0;
}

The server path in actions/setup/js/mcp_server_core.cjs is already schema-aware: empty-argument probe rejection is only relevant when the tool declares required fields. It accepts {} for a schema with no required inputs.

The compiler path in pkg/workflow/safe_outputs_config_generation.go also behaves correctly. generateCustomJobToolDefinition always emits an object schema, populates properties from configured inputs, and only adds required when at least one input is required. A custom job with omitted inputs therefore intentionally produces properties: {} and no required field.

Regression source and affected versions

The behavior was introduced by #43566 and merge commit 1ac3557.

The original change was intended to stop agents repeatedly probing write-once safe-output tools with {}, receiving -32602, and retrying until timeout. During review, schema-aware detection was simplified based on the assertion that safe-output tools always require arguments: #43566 (comment)

That assertion does not hold for custom safe-output jobs.

Version evidence:

Version Status
setup v0.81.6 / AWF v0.27.11 Known good
v0.82.3 First affected release containing the merge commit
v0.83.4 Affected
v0.84.0 Affected

The known-good lock file already generated an empty schema, confirming this is bridge behavior drift rather than a downstream schema change.

Similar-issue research

Before filing, searches were performed for the regression PR number, shouldShowToolHelpForEmptyArgs, zero-argument safeoutputs, empty-argument safeoutputs, and dispatch_code_factory. No existing gh-aw issue or fix PR describing this exact regression was found. The downstream issue linked above is the only identified report.

Implementation plan

Please have a core-team coding agent implement the following steps in order. Each step identifies the files, behavior, tests, and validation needed for a complete fix.

1. Restore schema-aware probe detection

Update shouldShowToolHelpForEmptyArgs in actions/setup/js/mcp_cli_bridge.cjs to receive the already-resolved matchedTool.

For an empty parsed argument object:

  • return false for servers other than safeoutputs;
  • inspect matchedTool.inputSchema.required;
  • show help only when required is a non-empty array;
  • allow zero-input and optional-only tools to proceed with {}.

Using required rather than the number of properties follows JSON Schema semantics: an optional-only tool can also be validly invoked with {}. Missing or malformed cached schema should not be misclassified as a required-input probe; the MCP server remains authoritative and can return the appropriate protocol error.

Pass matchedTool at the call site in main() and update the JSDoc. Do not remove the existing -32602 non-retry hint added by #43566; the fix should preserve that protection for invalid calls.

2. Add focused bridge tests

Update actions/setup/js/mcp_cli_bridge.test.cjs:

  • empty args + explicit empty object schema returns false from the help guard;
  • empty args + optional-only schema returns false;
  • empty args + non-empty required returns true;
  • non-empty calls and non-safeoutputs servers remain unchanged;
  • printf '{}' | safeoutputs dispatch_code_factory . reaches MCP tools/call with arguments: {};
  • bare safeoutputs dispatch_code_factory reaches the same call;
  • a normal required-input tool still prints help and does not call MCP.

Use a local in-process HTTP server for the bridge-level tests so the assertions cover the actual initialize → initialized notification → tools/call route without a live external API.

3. Preserve the compiler/runtime contract

Retain the existing no inputs unit case in pkg/workflow/safe_outputs_config_generation_test.go.

Extend pkg/workflow/safe_outputs_tools_meta_integration_test.go with a custom safe-output job whose inputs are omitted. After compilation, parse GH_AW_TOOLS_META_JSON and assert its dynamic_tools entry contains:

  • normalized tool name;
  • type: object;
  • empty properties;
  • no required key;
  • additionalProperties: false.

Note: explicit inputs: {} is currently rejected by the workflow schema's minProperties: 1; omitted inputs are the supported equivalent and generate the empty MCP schema at issue here.

4. Release metadata

Add a patch changeset describing restoration of valid zero-argument custom safe-output calls. This is a backward-compatible bug fix restoring the compiler/runtime contract, not a CLI breaking change.

No user documentation change should be required: the public configuration is already valid and the defect is in runtime routing. If maintainers want to clarify zero-input custom jobs explicitly, that can be handled as a separate documentation improvement.

5. Validation

The implementing core-team agent should work in the supported Dev Container or Codespace and run the repository-prescribed checks, including make agent-finish, before completing its PR.

Focused checks:

cd actions/setup/js
npm run typecheck
vitest run mcp_cli_bridge.test.cjs --no-file-parallelism

cd ../../..
go test ./pkg/workflow -run TestGenerateCustomJobToolDefinition
go test -tags=integration ./pkg/workflow -run TestToolsMetaJSONCompiledWorkflowEmbedsMeta

Reference implementation and local verification

A reference implementation was built locally at tobio/gh-aw@c159b95877. The issue is intentionally self-contained so a core-team agent can implement or adapt it according to the repository's contribution model.

Checks already completed against that reference implementation:

  • make build
  • make lint-cjs
  • npm run typecheck
  • focused bridge suite: 108 tests passed
  • go test ./pkg/workflow -run TestGenerateCustomJobToolDefinition: 11 tests passed
  • go test -tags=integration ./pkg/workflow -run TestToolsMetaJSONCompiledWorkflowEmbedsMeta: passed
  • make agent-report-progress: passed after installing the repository-pinned golangci-lint version

This local verification supplements, but does not replace, the required supported-environment validation by the implementing core-team agent.

Acceptance criteria

  • Empty calls to safe-output tools with no required fields reach MCP tools/call with {}.
  • Both bare invocation and piped {} invocation work for a zero-input custom tool.
  • Optional-only tools are not incorrectly blocked.
  • Empty calls to tools with required fields still show help and do not call MCP.
  • Non-safeoutputs behavior remains unchanged.
  • The existing -32602 non-retry hint remains intact.
  • Compiler integration coverage protects the empty-schema contract.
  • A patch changeset is included.
  • Focused tests and make agent-finish pass in the supported development environment.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions