Command Execution
How Ahoy finds your config
Section titled “How Ahoy finds your config”When you run an Ahoy command, it:
- Checks for a
--file(or-f) flag to use a specific config file - Otherwise, searches upward from the current directory for
.ahoy.yml - Changes to the directory containing that file
- Executes your command relative to that directory
This means you can run ahoy from any subdirectory in your project and it always behaves consistently.
Arguments
Section titled “Arguments”Arguments are passed to commands as bash positional parameters:
commands: greet: cmd: echo "Hello, $1!"
copy: cmd: cp $1 $2
run-in-container: cmd: docker-compose exec app "$@"ahoy greet World # Hello, World!ahoy copy src.txt dest.txtahoy run-in-container npm test$@ passes all arguments through, which is handy for wrapper commands. Use $# to check the argument count, and $0 for the command name.
Everything after the command name reaches your command verbatim, flags included, so wrapping a tool that takes its own options needs nothing special:
commands: phpunit: cmd: docker compose exec app vendor/bin/phpunit "$@"ahoy phpunit --filter=MyTest --stop-on-failureAhoy’s own flags - --verbose, --file and friends - are only recognised
before the command name, so they never collide with your command’s options.
Interactive commands
Section titled “Interactive commands”All three standard streams - stdin, stdout and stderr - are inherited from the process that invoked Ahoy. Ahoy does not read, buffer or copy them, so a command runs exactly as it would if you had typed it yourself:
commands: shell: usage: Open a shell in the app container cmd: docker-compose exec app bash
mysql: usage: Open a MySQL shell cmd: mysql -u$DB_USER -p$DB_PASS $DB_NAMEWhen you run these from a terminal, the inherited streams are attached to a TTY, so they run fully interactively - you get a real prompt, readline support, and piping all work as expected. Invoked from a script or CI job, where the streams are pipes or files instead, the command sees exactly what it would see there without Ahoy.
Full-screen and TUI commands
Section titled “Full-screen and TUI commands”Because the streams are the inherited descriptors rather than a copy of them, commands can ask about them directly - whether they are a TTY at all, and if so the terminal size - and get a true answer. When Ahoy is run from a terminal that is what full-screen and menu-driven tools need in order to draw:
commands: pick-env: usage: Choose an environment to deploy to cmd: gum choose staging production
logs: usage: Browse container logs interactively cmd: docker compose logs --tail=1000 | fzf --tacTools built on Bubble Tea - gum,
glow and similar - draw on stderr specifically, as do progress bars, spinners
and anything that repaints in place.
Output is not buffered either - a long-running command’s output appears as it is produced, not in a burst when the command exits.
Chaining commands
Section titled “Chaining commands”You can call other Ahoy commands from within a command:
commands: ci: usage: Run the full CI pipeline locally cmd: | ahoy install ahoy lint ahoy test
install: cmd: npm install
lint: cmd: npm run lint
test: cmd: npm testCustom entrypoints
Section titled “Custom entrypoints”The default entrypoint is bash -c '{{cmd}}'. Override it at the file level to use a different interpreter:
ahoyapi: v2
entrypoint: - node - "-e" - "{{cmd}}"
commands: hello: cmd: console.log("Hello from Node.js!")ahoyapi: v2
entrypoint: - python3 - "-c" - "{{cmd}}"
commands: hello: cmd: print("Hello from Python!")Entrypoint template variables:
{{cmd}}- replaced with the command body{{name}}- replaced with the command name (also available as$0)
Exit codes
Section titled “Exit codes”Ahoy passes exit codes through unchanged. If a command exits non-zero, Ahoy exits non-zero. This works naturally with shell pipelines:
ahoy test && ahoy deployControlling execution within a command
Section titled “Controlling execution within a command”Because the command body is bash, the usual operators control what runs next:
&&- run the next command only if the previous one succeeded (exit0)||- run the next command only if the previous one failed (non-zero exit);- always run the next command
commands: only-run-1-2-and-4: cmd: | echo "1 - If this passes" && echo "2 - Then do this" || echo "3 - Or do this if 1 or 2 fails" ; echo "4 - Do this no matter what"Debugging and gotchas
Section titled “Debugging and gotchas”- Use
--verboseto see what actually ran. It prints the resolved command before executing it, which is the fastest way to diagnose an unexpected result. - Commands run in a subshell. Environment variables are copied in, but nothing a command does can change your parent shell - it cannot change your current directory or set variables for your session.
- Quotes can be tricky. When passing a command through to a subcommand, quotes can be lost along the way. Check
--verbosefirst, then experiment with single versus double quotes. Prefercmd: |for anything complex so YAML quoting stays out of the way. - Check your YAML if Ahoy complains. Indentation and structure errors surface as parse failures;
ahoy config validatereports them along with unsupported fields.