8.2 KiB
8.2 KiB
Quick Reference: Action Parameter Handling
Last Updated: 2026-02-07
Status: Current standard for all actions
TL;DR
- ✅ DO: Read action parameters from stdin as JSON
- ❌ DON'T: Use environment variables for action parameters
- 💡 Environment variables are for debug/config only (e.g.,
DEBUG=1)
Secure Parameter Delivery
All action parameters are delivered via stdin in JSON format to prevent exposure in process listings.
YAML Configuration
name: my_action
ref: mypack.my_action
runner_type: shell # or python, nodejs
entry_point: my_action.sh
# Always specify stdin parameter delivery
parameter_delivery: stdin
parameter_format: json
parameters:
type: object
properties:
message:
type: string
default: "Hello"
api_key:
type: string
secret: true # Mark sensitive parameters
Implementation Patterns
Bash/Shell Actions
#!/bin/bash
set -e
set -o pipefail
# Read JSON parameters from stdin
INPUT=$(cat)
# Parse parameters with jq (includes default values)
MESSAGE=$(echo "$INPUT" | jq -r '.message // "Hello, World!"')
API_KEY=$(echo "$INPUT" | jq -r '.api_key // ""')
COUNT=$(echo "$INPUT" | jq -r '.count // 1')
ENABLED=$(echo "$INPUT" | jq -r '.enabled // false')
# Handle optional parameters (check for null)
if [ -n "$API_KEY" ] && [ "$API_KEY" != "null" ]; then
echo "API key provided"
fi
# Use parameters
echo "Message: $MESSAGE"
echo "Count: $COUNT"
Python Actions
#!/usr/bin/env python3
import json
import sys
from typing import Dict, Any
def read_parameters() -> Dict[str, Any]:
"""Read and parse JSON parameters from stdin."""
try:
input_data = sys.stdin.read()
if not input_data:
return {}
return json.loads(input_data)
except json.JSONDecodeError as e:
print(f"ERROR: Invalid JSON input: {e}", file=sys.stderr)
sys.exit(1)
def main():
# Read parameters
params = read_parameters()
# Access parameters with defaults
message = params.get('message', 'Hello, World!')
api_key = params.get('api_key')
count = params.get('count', 1)
enabled = params.get('enabled', False)
# Validate required parameters
if not params.get('url'):
print("ERROR: 'url' parameter is required", file=sys.stderr)
sys.exit(1)
# Use parameters
print(f"Message: {message}")
print(f"Count: {count}")
# Output result as JSON
result = {"status": "success", "message": message}
print(json.dumps(result))
if __name__ == "__main__":
main()
Node.js Actions
#!/usr/bin/env node
const readline = require('readline');
async function readParameters() {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
terminal: false
});
let input = '';
for await (const line of rl) {
input += line;
}
try {
return JSON.parse(input || '{}');
} catch (err) {
console.error('ERROR: Invalid JSON input:', err.message);
process.exit(1);
}
}
async function main() {
// Read parameters
const params = await readParameters();
// Access parameters with defaults
const message = params.message || 'Hello, World!';
const apiKey = params.api_key;
const count = params.count || 1;
const enabled = params.enabled || false;
// Use parameters
console.log(`Message: ${message}`);
console.log(`Count: ${count}`);
// Output result as JSON
const result = { status: 'success', message };
console.log(JSON.stringify(result, null, 2));
}
main().catch(err => {
console.error('ERROR:', err.message);
process.exit(1);
});
Testing Actions Locally
# Test with specific parameters
echo '{"message": "Test", "count": 5}' | ./my_action.sh
# Test with defaults (empty JSON)
echo '{}' | ./my_action.sh
# Test with file input
cat test-params.json | ./my_action.sh
# Test Python action
echo '{"url": "https://api.example.com"}' | python3 my_action.py
# Test with multiple parameters including secrets
echo '{"url": "https://api.example.com", "api_key": "secret123"}' | ./my_action.sh
Environment Variables Usage
✅ Correct Usage (Configuration/Debug)
# Debug logging control
DEBUG=1 ./my_action.sh
# Log level control
LOG_LEVEL=debug ./my_action.sh
# System configuration
PATH=/usr/local/bin:$PATH ./my_action.sh
❌ Incorrect Usage (Parameters)
# NEVER do this - parameters should come from stdin
ATTUNE_ACTION_MESSAGE="Hello" ./my_action.sh # ❌ WRONG
API_KEY="secret" ./my_action.sh # ❌ WRONG - exposed in ps!
Common Patterns
Required Parameters
# Bash
URL=$(echo "$INPUT" | jq -r '.url // ""')
if [ -z "$URL" ] || [ "$URL" == "null" ]; then
echo "ERROR: 'url' parameter is required" >&2
exit 1
fi
# Python
if not params.get('url'):
print("ERROR: 'url' parameter is required", file=sys.stderr)
sys.exit(1)
Optional Parameters with Null Check
# Bash
API_KEY=$(echo "$INPUT" | jq -r '.api_key // ""')
if [ -n "$API_KEY" ] && [ "$API_KEY" != "null" ]; then
# Use API key
echo "Authenticated request"
fi
# Python
api_key = params.get('api_key')
if api_key:
# Use API key
print("Authenticated request")
Boolean Parameters
# Bash - jq outputs lowercase 'true'/'false'
ENABLED=$(echo "$INPUT" | jq -r '.enabled // false')
if [ "$ENABLED" = "true" ]; then
echo "Feature enabled"
fi
# Python - native boolean
enabled = params.get('enabled', False)
if enabled:
print("Feature enabled")
Array Parameters
# Bash
ITEMS=$(echo "$INPUT" | jq -c '.items // []')
ITEM_COUNT=$(echo "$ITEMS" | jq 'length')
echo "Processing $ITEM_COUNT items"
# Python
items = params.get('items', [])
print(f"Processing {len(items)} items")
for item in items:
print(f" - {item}")
Object Parameters
# Bash
HEADERS=$(echo "$INPUT" | jq -c '.headers // {}')
# Extract specific header
AUTH=$(echo "$HEADERS" | jq -r '.Authorization // ""')
# Python
headers = params.get('headers', {})
auth = headers.get('Authorization')
Security Best Practices
- Never log sensitive parameters - Avoid printing secrets to stdout/stderr
- Mark secrets in YAML - Use
secret: truefor sensitive parameters - No parameter echoing - Don't echo input JSON back in error messages
- Clear error messages - Don't include parameter values in errors
- Validate input - Check parameter types and ranges
Example: Safe Error Handling
# ❌ BAD - exposes parameter value
if not valid_url(url):
print(f"ERROR: Invalid URL: {url}", file=sys.stderr)
# ✅ GOOD - generic error message
if not valid_url(url):
print("ERROR: 'url' parameter must be a valid HTTP/HTTPS URL", file=sys.stderr)
Migration from Environment Variables
If you have existing actions using environment variables:
# OLD (environment variables)
MESSAGE="${ATTUNE_ACTION_MESSAGE:-Hello}"
COUNT="${ATTUNE_ACTION_COUNT:-1}"
# NEW (stdin JSON)
INPUT=$(cat)
MESSAGE=$(echo "$INPUT" | jq -r '.message // "Hello"')
COUNT=$(echo "$INPUT" | jq -r '.count // 1')
# OLD (environment variables)
import os
message = os.environ.get('ATTUNE_ACTION_MESSAGE', 'Hello')
count = int(os.environ.get('ATTUNE_ACTION_COUNT', '1'))
# NEW (stdin JSON)
import json, sys
params = json.loads(sys.stdin.read() or '{}')
message = params.get('message', 'Hello')
count = params.get('count', 1)
Dependencies
- Bash: Requires
jq(installed in all Attune worker containers) - Python: Standard library only (
json,sys) - Node.js: Built-in modules only (
readline)
References
- Core Pack Actions README - Reference implementations
- Secure Action Parameter Handling Formats - Design document
- Worker Service Architecture - Parameter delivery details
See Also
- Environment variables via
execution.env_vars(for runtime context) - Secret management via
keytable (for encrypted storage) - Parameter validation in action YAML schemas