A Look at Hook Execution in Orca's orca.yaml
#On this page
AI developer tools are increasingly executing logic directly from repositories—setup steps, configuration, and hooks.
While looking through Orca, I came across a simple example of repository-defined configuration becoming local shell execution.
Context
Tools like Orca are doing more than traditional editors. In many cases, they:
- Clone repositories
- Parse configuration files
- Run setup steps
- Execute project-defined hooks
This makes workflows smoother, but it also means repository content can influence what runs locally.
What I Observed
While exploring the codebase, I was generally looking for:
- External API calls
- Interactions with local services
- Calls to
execor other process-spawning functions
Following that path led to how Orca handles hooks defined in orca.yaml.
These hooks are executed using:
exec(script, { shell: ... })execFile(... 'bash', '-c', ...)(via WSL)
In other words, commands defined in the repository are executed directly in a local shell environment.
A minimal example:
# Example orca.yaml snippet
scripts:
setup: |
echo "setup hook executed"
open -a CalculatorWhen a repository defines a hook like this, that command runs locally as part of the workflow.
In practice, this ran automatically during normal worktree lifecycle actions like:
- Creating a worktree
- Deleting a worktree
In my test case, this meant the Calculator app would open as those actions occurred.
This creates a simple but important behavior:
Opening or working with a repository can trigger execution defined entirely within that repository.
Implementation Detail
At a high level, the flow looks like:
repository config → hook resolution → shell execution
In code, that starts with resolving the hook from repository configuration:
const hooks = getEffectiveHooks(repo)
const script = hooks?.scripts[hookName]
if (!script) {
return Promise.resolve({ success: true, output: '' })
}Standard (macOS / Linux) Execution Path
In the standard execution path, the script is passed directly into a shell:
exec(script, { cwd, timeout: HOOK_TIMEOUT }, (error, stdout, stderr) => {
// handle result
})This is the key boundary: the contents of orca.yaml become shell input.
At its simplest, this becomes:
repository-controlled string → shell execution
Windows / WSL Execution Path
In WSL environments, the same script is routed through bash -c:
const escapedCwd = wslInfo.linuxPath.replace(/'/g, "'\\''")
const escapedScript = script.replace(/'/g, "'\\''")
const bashCmd = `cd '${escapedCwd}' && ${escapedScript}`
execFile(
'wsl.exe',
['-d', wslInfo.distro, '--', 'bash', '-c', bashCmd],
{
timeout: HOOK_TIMEOUT,
encoding: 'utf-8',
env: { ...process.env, ...wslEnv }
},
...
)Why It's Interesting
This isn’t unique to Orca—many tools support hooks or setup scripts.
What stood out is how naturally execution becomes tied to normal workflows like creating or operating on a worktree, rather than an explicit action like running a script.
Contribution
I opened a PR to help refine how this behavior is handled:
https://github.com/stablyai/orca/pull/1138
Huge thanks to the Orca / Stably AI team for being responsive and thoughtful. The maintainer jumped on this quickly and did the bulk of the work to refine the overall approach.
The resulting changes introduced a more explicit trust model around hook execution:
- Hook execution is gated behind a prompt the first time it runs in a repo (per hook type)
- Approvals are persisted using a content hash of the script, so unchanged hooks don’t re-prompt across restarts
- If
orca.yamlchanges, the hash no longer matches and the user is prompted again with the updated script - An “always trust this repository” option allows users to bypass all future hook prompts for that repo
This keeps hooks as a useful feature, while making their execution more visible, intentional, and consistent.
Broader Observation
As more tools move toward automation and agent-like workflows, they increasingly:
- Treat configuration as executable behavior
- Run steps on behalf of the user
- Optimize for reducing manual setup
That's a powerful shift, but it makes trust boundaries less obvious by default.
Takeaway
For tools in this space, it's worth being deliberate about:
- When repository-defined logic is executed
- How visible that execution is to the user
- Where explicit trust decisions happen
Small implementation details like this can have a meaningful impact on how these tools behave in practice.