Skip to main content
Version: Next

Custom Check Dependencies

Custom checks let you write validation scripts for requirements that don't fit the built-in dependency types. Check tool versions, configuration validity, or any other custom requirement.

Basic Usage

depends_on: {
custom_checks: [
{
alternatives: [{
name: "docker-running"
script: {content: "docker info > /dev/null 2>&1"}
}]
}
]
}

If the check fails:

✗ Dependencies not satisfied

Command 'build' has unmet dependencies:

Failed Custom Checks:
• go-version - check script returned non-zero exit code

Ensure all requirements are met and try again.

Check Properties

PropertyRequiredDescription
nameYesIdentifier for error messages
script.contentYesInline script content to execute for the check
script.fileModule onlyScript file contained in the source invowkmod
script.interpreterNoInterpreter for the resolved script content; defaults to shebang detection and then the embedded mvdan/sh shell for host checks
expected_codeNoExpected exit code (default: 0)
expected_outputNoRegex pattern to match output

:::info Host and Container Execution Root, command, and implementation-level custom checks always run on the host. To run a custom check inside a container image, put depends_on inside the selected container runtime block. :::

Host custom checks without a shebang or script.interpreter run through Invowk's embedded mvdan/sh shell. A non-shell shebang or script.interpreter such as python3 runs through that host interpreter. Container custom checks use the same script.interpreter field inside the selected container. If a concrete script.interpreter overrides a different shebang, Invowk reports an advisory warning and keeps the explicit interpreter authoritative.

Exit Code Validation

By default, a check passes if the script exits with code 0:

custom_checks: [
{
name: "docker-running"
script: {content: "docker info > /dev/null 2>&1"}
// Passes if exit code is 0
}
]

Expect a different exit code:

custom_checks: [
{
name: "not-production"
script: {content: """
test "$ENV" = 'production'
"""}
expected_code: 1 // Should fail (not be production)
}
]

Output Validation

Check that output matches a pattern:

custom_checks: [
{
name: "node-version"
script: {content: "node --version"}
expected_output: "^v(18|20|22)\\." // Major version 18, 20, or 22
}
]

Both conditions must pass when used together:

custom_checks: [
{
name: "go-version"
script: {content: "go version"}
expected_code: 0 // Must succeed
expected_output: "go1\\.2[6-9]" // Must be Go 1.26+
}
]

Alternatives (OR Semantics)

Provide alternative checks:

custom_checks: [
{
alternatives: [
{
name: "go-1.26"
script: {content: "go version | grep -q 'go1.26'"}
},
{
name: "go-1.27"
script: {content: "go version | grep -q 'go1.27'"}
}
]
}
]

The dependency is satisfied if any alternative passes.

Real-World Examples

Tool Version Check

{
name: "build"
depends_on: {
tools: [{alternatives: ["go"]}]
custom_checks: [
{
name: "go-1.26-or-higher"
script: {content: """
version=$(go version | grep -oE 'go[0-9]+.[0-9]+' | head -1)
major=$(echo $version | cut -d. -f1 | tr -d 'go')
minor=$(echo $version | cut -d. -f2)
[ "$major" -ge 1 ] && [ "$minor" -ge 26 ]
"""}
}
]
}
implementations: [...]
}

Docker Running Check

{
name: "docker-build"
depends_on: {
tools: [{alternatives: ["docker"]}]
custom_checks: [
{
name: "docker-daemon"
script: {content: "docker info > /dev/null 2>&1"}
}
]
}
implementations: [{
script: {content: "docker build -t myapp ."}
runtimes: [{name: "native"}]
platforms: [{name: "linux"}, {name: "macos"}]
}]
}

Git Status Check

{
name: "release"
depends_on: {
tools: [{alternatives: ["git"]}]
custom_checks: [
{
name: "clean-working-tree"
script: {content: """
test -z "$(git status --porcelain)"
"""}
},
{
name: "on-main-branch"
script: {content: """
test "$(git branch --show-current)" = 'main'
"""}
}
]
}
implementations: [{
script: {content: "./scripts/release.sh"}
runtimes: [{name: "native"}]
platforms: [{name: "linux"}, {name: "macos"}]
}]
}

Configuration Validation

{
name: "deploy"
depends_on: {
filepaths: [{alternatives: ["config.yaml"]}]
custom_checks: [
{
name: "valid-yaml"
script: {content: """
python3 -c 'import yaml; yaml.safe_load(open("config.yaml"))'
"""}
},
{
name: "has-required-fields"
script: {content: """
grep -q 'database:' config.yaml &&
grep -q 'server:' config.yaml
"""}
}
]
}
implementations: [...]
}

Memory/Resource Check

{
name: "build heavy"
depends_on: {
custom_checks: [
{
name: "enough-memory"
script: {content: """
# Check for at least 4GB free memory
free_mb=$(free -m | awk '/^Mem:/{print $7}')
[ "$free_mb" -ge 4096 ]
"""}
},
{
name: "enough-disk"
script: {content: """
# Check for at least 10GB free disk
free_gb=$(df -BG . | awk 'NR==2{print $4}' | tr -d 'G')
[ "$free_gb" -ge 10 ]
"""}
}
]
}
implementations: [...]
}

Kubernetes Context

{
name: "deploy"
depends_on: {
tools: [{alternatives: ["kubectl"]}]
custom_checks: [
{
name: "correct-context"
script: {content: "kubectl config current-context"}
expected_output: "^production-cluster$"
},
{
name: "cluster-reachable"
script: {content: "kubectl cluster-info > /dev/null 2>&1"}
}
]
}
implementations: [...]
}

Multiple Version Options

{
name: "build"
depends_on: {
custom_checks: [
{
alternatives: [
{
name: "python-3.11"
script: {content: "python3 --version"}
expected_output: "^Python 3.11"
},
{
name: "python-3.12"
script: {content: "python3 --version"}
expected_output: "^Python 3.12"
}
]
}
]
}
implementations: [...]
}

Container Context

Runtime-level custom checks inside a container runtime block run inside the container:

{
name: "build"
implementations: [{
script: {content: "echo running custom checks"}
runtimes: [{
name: "container"
image: "debian:stable-slim"
depends_on: {
custom_checks: [
{
name: "shell-ready"
// This runs INSIDE the container
script: {content: "test -x /bin/sh && echo ok"}
expected_output: "^ok$"
}
]
}
}]
platforms: [{name: "linux"}]
}]
}

Script Tips

Keep Scripts Simple

// Good - simple and clear
script: {content: "go version | grep -q 'go1.26'"}

// Avoid - complex and fragile
script: {content: """
set -e
version=$(go version 2>&1)
if [ $? -ne 0 ]; then exit 1; fi
echo "$version" | grep -qE 'go1.(2[6-9]|[3-9][0-9])'
"""}

Use Proper Exit Codes

// Script should exit 0 for success, non-zero for failure
script: {content: """
if [ -f "required-file" ]; then
exit 0
else
exit 1
fi
"""}

Handle Missing Commands

script: {content: "command -v mytools > /dev/null && mytool --check"}

Best Practices

  1. Name checks clearly: Use descriptive names for error messages
  2. Keep scripts simple: One check, one purpose
  3. Use exit codes: Return 0 for success, non-zero for failure
  4. Add output validation: When version format matters
  5. Consider alternatives: Offer multiple valid configurations

Next Steps