Files
attune/work-summary/2026-02-action-execution-fixes.md

8.9 KiB

Action Execution Fixes - 2026-02-04

Summary

Fixed three critical issues with action execution identified during testing:

  1. Correct result format for stdout/stderr - stdout content included directly in result JSON, stderr written to log file with path included only if non-empty/non-whitespace
  2. Fixed parameter name case sensitivity - Worker now preserves exact parameter names from schema (lowercase) instead of uppercasing them
  3. Eliminated jq dependency in core shell actions - Converted echo, noop, and sleep actions to use pure POSIX shell with DOTENV parameter format

Issues Addressed

Issue 1: Correct Result Format for stdout/stderr

Problem: Initial implementation had stdout/stderr content duplicated in both result JSON and log files, causing confusion about which was the source of truth.

Correct Specification:

  • stdout → included directly in the result JSON payload (primary output)
  • stderr → written to log file; log file path included in result JSON ONLY if stderr is non-empty and non-whitespace
  • Both stdout and stderr are always written to artifact log files for persistence

Solution: Modified executor.rs to implement correct result format:

  • handle_execution_success(): Include stdout content in result, stderr_log path only if stderr has content
  • handle_execution_failure(): Include stdout content in result, stderr_log path only if stderr has content
  • Result structure:
    {
      "exit_code": 0,
      "duration_ms": 5,
      "succeeded": true,
      "stdout": "Action output here\n",
      "stderr_log": "/tmp/attune/artifacts/execution_123/stderr.log"  // only if stderr non-empty
    }
    

Benefits:

  • stdout immediately available in result for quick access
  • stderr log path provided only when there's actual error output to review
  • Clear separation: stdout for primary output, stderr for diagnostics
  • Artifact log files preserve complete history

Issue 2: Parameter Name Case Sensitivity

Problem: The worker was converting parameter names to uppercase in DOTENV format:

let key_upper = key.to_uppercase();  // Wrong!
lines.push(format!("{}='{}'", key_upper, escaped_value));

This broke shell scripts that expected lowercase parameter names matching the schema:

  • Schema: message (lowercase)
  • Worker sent: MESSAGE='value' (uppercase)
  • Script expected: message='value' (lowercase)

Root Cause: Parameter names are case-sensitive in shell. The action YAML schemas define exact parameter names (e.g., message, exit_code, seconds) which must be preserved exactly.

Solution: Modified parameter_passing.rs to preserve original parameter names:

// Before
let key_upper = key.to_uppercase();
lines.push(format!("{}='{}'", key_upper, escaped_value));

// After
lines.push(format!("{}='{}'", key, escaped_value));

Updated tests to expect lowercase parameter names.

Impact:

  • Parameter names now match exactly what's defined in action schemas
  • Shell scripts can use correct lowercase variable names
  • No transformation applied - what's in the schema is what gets passed

Issue 3: jq Dependency in Core Shell Actions

Problem: Core shell actions (echo, noop, sleep) used jq for JSON parsing:

MESSAGE=$(echo "$INPUT" | jq -r '.message // ""')

But jq is not available in the Docker worker containers, causing execution failures:

exit_code: 127
error: "/opt/attune/packs/core/actions/echo.sh: line 12: jq: command not found"

Principle: Built-in shell scripts should be independent from the installed system and prefer POSIX-compliant shell operators over external utilities.

Solution:

  1. Rewrote actions to parse DOTENV format using pure POSIX shell:

    • echo.sh: Parse message parameter
    • noop.sh: Parse message and exit_code parameters
    • sleep.sh: Parse seconds and message parameters
  2. Changed parameter format from json to dotenv in action YAML files:

    • echo.yaml
    • noop.yaml
    • sleep.yaml
  3. Implementation uses pure POSIX shell features:

    • #!/bin/sh instead of #!/bin/bash
    • No bashisms (no set -o pipefail, no [[ tests)
    • Pattern matching with case statements
    • String manipulation with parameter expansion (${var#prefix}, ${var%suffix})

Parsing Logic:

# Read DOTENV-formatted parameters from stdin until delimiter
while IFS= read -r line; do
    case "$line" in
        *"---ATTUNE_PARAMS_END---"*)
            break
            ;;
        message=*)
            message="${line#message=}"
            # Remove quotes if present
            case "$message" in
                \"*\") message="${message#\"}" ; message="${message%\"}" ;;
                \'*\') message="${message#\'}" ; message="${message%\'}" ;;
            esac
            ;;
    esac
done

Benefits:

  • Zero external dependencies (no jq, yq, or other tools required)
  • Works in minimal container environments
  • Faster execution (no subprocess spawning for jq)
  • More portable across different shell environments
  • POSIX-compliant for maximum compatibility

Note: Complex actions like http_request legitimately need JSON and external tools (curl, jq) for sophisticated processing. Only simple utility actions were converted to DOTENV format.

Additional Improvements

Enhanced Debug Logging

Added detailed logging to help diagnose execution status issues:

debug!(
    "Execution {} result: exit_code={}, error={:?}, is_success={}",
    execution_id,
    result.exit_code,
    result.error,
    is_success
);

This helps verify that is_success() correctly evaluates to false when exit_code is non-zero.

Files Modified

Worker Service

  • crates/worker/src/executor.rs - Removed stdout/stderr content from result JSON, added debug logging
  • crates/worker/src/runtime/parameter_passing.rs - Fixed case sensitivity (preserve original parameter names)

Core Pack Actions (Shell Scripts)

  • packs/core/actions/echo.sh - Rewrote to use pure POSIX shell + DOTENV parsing
  • packs/core/actions/noop.sh - Rewrote to use pure POSIX shell + DOTENV parsing
  • packs/core/actions/sleep.sh - Rewrote to use pure POSIX shell + DOTENV parsing

Core Pack Action Definitions (YAML)

  • packs/core/actions/echo.yaml - Changed parameter_format from json to dotenv
  • packs/core/actions/noop.yaml - Changed parameter_format from json to dotenv
  • packs/core/actions/sleep.yaml - Changed parameter_format from json to dotenv

Testing

Manual testing verified:

  1. POSIX shell scripts correctly parse lowercase parameter names
  2. Quote handling works (single quotes, double quotes, no quotes)
  3. Empty parameters handled correctly
  4. Parameter validation works (numeric checks, range checks)

Example test:

echo "message='Hello World!'" > test.txt
echo "---ATTUNE_PARAMS_END---" >> test.txt
cat test.txt | sh packs/core/actions/echo.sh
# Output: Hello World!

Design Decisions

Why DOTENV Over JSON for Simple Actions?

  1. Dependency-free: No need for jq, python, or other parsers
  2. Simple actions = simple format: Key-value pairs are natural for basic parameters
  3. Shell-friendly: Direct variable assignment pattern familiar to shell scripters
  4. Portable: Pure POSIX shell works everywhere

Why Keep JSON for Complex Actions?

Actions like http_request, download_packs, and build_pack_envs legitimately need:

  • Nested data structures (headers, query params)
  • Array handling
  • Complex object manipulation
  • Integration with JSON APIs

For these, JSON + jq is the right tool.

Parameter Format Selection Guidelines

Use DOTENV when:

  • Action has simple key-value parameters (strings, numbers, booleans)
  • No nested structures needed
  • Want to avoid external dependencies
  • Writing pure shell scripts

Use JSON when:

  • Need nested objects or arrays
  • Integrating with JSON APIs
  • Complex data manipulation required
  • Using Python/Node.js runtimes that parse JSON natively

Impact

Positive

  • Core actions now work in minimal Docker environments
  • Faster execution (no subprocess spawning for jq)
  • Cleaner result JSON (no log content duplication)
  • Correct parameter name handling (case-sensitive)
  • Better debugging (enhanced logging)

Compatibility

  • ⚠️ Breaking change: Actions using lowercase parameter names will now receive them correctly
  • ⚠️ Breaking change: Result JSON format changed - stdout now in result, stderr_log only if non-empty
  • No impact on JSON-based actions (http_request, etc.)

Next Steps

  1. Test updated actions in Docker environment
  2. Update any custom packs that relied on uppercase parameter names
  3. Update Web UI to display stdout from result JSON, stderr via log file endpoint if stderr_log path present
  4. Consider adding parameter format documentation to pack development guide
  • docs/packs/pack-structure.md - Parameter delivery and format options
  • docs/architecture/worker-service.md - Action execution flow
  • AGENTS.md - Project rules on parameter passing and shell scripts