Skip to content

devbox run hooks for pre, post, and command wrapping #2862

@levonk

Description

@levonk

What problem are you trying to solve?

I'm trying to wrap every Devbox-executed command with a command wrapper (specifically rtk exec --) for instrumentation, tracing, and policy enforcement without breaking reproducibility.

The core issue is that Devbox currently has no mechanism to intercept or wrap commands executed via devbox run. This makes it impossible to:

  1. Instrument commands: Add tracing/telemetry to every command execution
  2. Enforce policies: Validate or block commands before execution
  3. Sandbox commands: Wrap execution with security/policy tools
  4. Transform commands: Modify arguments, environment, or I/O dynamically

Devbox's execution model is:

devbox run <script> → spawn non-interactive shell → run command

Non-interactive shells always source $BASH_ENV or .zshenv, but this is a fragile workaround, not a proper solution.

What solution would you like?

I propose adding a comprehensive hook system under the existing shell configuration in devbox.json:

{
  "shell": {
    "init_hook": [...],
    "scripts": { ... },
    
    // NEW: Run hooks for devbox run
    "run_hooks": {
      "pre_run": [{
        "command": "policy-checker --cmd {cmd} --args {args}",
        "can_block": true,
        "can_modify_args": false,
        "can_modify_env": false,
        "can_modify_stdin": false
      }],
      "command_wrapper": ["rtk exec --"],
      "post_run": [{
        "command": "telemetry-collector --exit {exit} --stdout {stdout}",
        "can_modify_exit": true,
        "can_modify_stdout": false,
        "can_modify_stderr": false
      }]
    }
  }
}

Three hook types:

  1. pre_run: Runs before command execution

    • can_block: Hook can refuse execution (return non-zero)
    • can_modify_args: Hook can change command arguments
    • can_modify_env: Hook can change environment variables
    • can_modify_stdin: Hook can change stdin input
  2. command_wrapper: Simple string wrapper for the entire command

    • Executes: rtk exec -- <original command>
    • Use case: Simple instrumentation wrappers
  3. post_run: Runs after command execution

    • can_modify_exit: Hook can change the exit code
    • can_modify_stdout: Hook can change stdout
    • can_modify_stderr: Hook can change stderr
    • Receives: exit code, stdout, stderr as context

Key design principles:

  • Explicit capability gates: Each hook declares what it can do (security, clarity, validation)
  • Symmetric design: Both pre and post hooks have consistent gating
  • Default-deny: Dangerous capabilities are opt-in
  • Follows Devbox conventions: Uses existing shell namespace
  • Backward compatible: All hooks are optional

Additional context:

This feature would enable:

  • Security tools: Policy enforcement, sandboxing, secret scanning
  • Observability: Telemetry, tracing, execution logging
  • Development workflow: Input validation, output formatting, conditional execution
  • Tool integration: OpenTelemetry, Hermes, policy agents, sandbox wrappers

The implementation would be straightforward (wrap the exec call in the devbox run codepath) and high-value for the ecosystem without breaking reproducibility when properly configured.

Alternatives you've considered

  1. Devbox shell hooks → Only run for devbox shell, not devbox run
  2. Devbox scripts → Requires wrapping each script manually (tedious, error-prone)
  3. PATH shims → Devbox resolves commands inside the environment, bypassing wrappers
  4. Aliasing → Aliases don't apply in non-interactive shells
  5. $BASH_ENV/.zshenv workaround → Fragile, doesn't provide proper control flow or access to execution context
  6. post_run hooks only → Can't prevent execution or modify inputs

This design matches patterns from other tools:

  • Nix flakes: apps.<name>.program wrapping
  • direnv: Hook mechanisms for environment setup
  • Bazel: Toolchain wrapping and hooks
  • Git: pre-commit/post-commit hooks
  • CI/CD systems: before_script/after_script stages

Metadata

Metadata

Assignees

No one assigned

    Labels

    featureNew feature or requesttriageIssue needs triage

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions