Debugger
argsh includes a Debug Adapter Protocol (DAP) server that provides step-through debugging for argsh and bash scripts directly in VSCode. Set breakpoints, step through code, inspect variables, and navigate the call stack — no set -x or echo debugging needed.
How it works
The debugger uses bash's built-in DEBUG trap — no external debugger (bashdb) is required. When you start a debug session:
- argsh-dap injects a lightweight debug prelude into your script
- The prelude sets up a
trap DEBUGhandler that checks for breakpoints and step commands - Communication between the debugger and your script happens via named pipes (FIFOs)
- VSCode's debug UI (breakpoints, call stack, variables) is powered by the standard DAP protocol
Quick start
- Open any
.shor argsh script in VSCode - Set a breakpoint by clicking in the gutter (left of line numbers)
- Press F5 (or Run → Start Debugging)
- Select argsh: Debug Script if prompted
- The script runs and stops at your breakpoints
Launch configuration
Create a .vscode/launch.json in your project:
Configuration options
| Option | Type | Default | Description |
|---|---|---|---|
program | string | required | Path to the script to debug |
args | string[] | [] | Arguments to pass to the script |
cwd | string | ${workspaceFolder} | Working directory |
env | object | {} | Environment variables |
stopOnEntry | boolean | true | Stop on the first executable line |
Features
Breakpoints
Click in the gutter to set breakpoints. Breakpoints work in the main script and in any file loaded via source or import.
Smart breakpoints (by subcommand name)
Set a breakpoint on a :usage subcommand by name instead of a specific line number. The debugger resolves the name through the namespace chain (main::deploy, deploy::run, argsh::deploy, deploy) to find the target function.
Use Run → New Breakpoint → Function Breakpoint and enter the subcommand name (e.g., deploy). The debugger resolves it to the actual function's file and line, following the same resolution rules as :usage.
This also works across files — if the target function is in an imported module, the resolver follows the import chain to find it.
Stepping
| Action | Shortcut | Description |
|---|---|---|
| Continue | F5 | Run until the next breakpoint |
| Step Over | F10 | Execute the current line, step over function calls |
| Step Into | F11 | Step into function calls |
| Step Out | Shift+F11 | Run until the current function returns |
Call stack
The call stack panel shows the current function call chain with file names and line numbers. argsh's namespace resolution (main::deploy, deploy::run) is preserved in the stack display.
Variable inspection
The Variables panel shows two scopes when stopped at a breakpoint:
- Locals — runtime variable values from the bash process (planned — not yet implemented in the MVP; will use
declare -pvia FIFO round-trip) - argsh Args — static view of the
:argsfield definitions with types (available now)
argsh Args inspector
When stopped inside a function that uses :args, the Variables panel shows an argsh Args scope with a structured view of the args array definition:
- Each entry shows the field spec, description, and type
- Flags are annotated with their modifiers (
:+boolean,:~inttyped,:!required) - Positional arguments are distinguished from flags
This gives you immediate visibility into what arguments the current function expects without reading the source.
Variable type tooltips
Hover over a variable name while debugging to see its argsh type annotation. For example, hovering over port in a function with 'port|p:~int' shows:
This works for all variables that appear in :args definitions — flags, positionals, and typed fields.
Runtime values in hover tooltips are planned for a future release. Currently only the argsh field definition is shown.
Auto-generated launch configurations
The debugger can generate launch configurations for every subcommand path in your script's :usage tree. Each subcommand becomes a separate debug target with the correct args pre-filled.
For a script with subcommands deploy, deploy staging, and status, the debugger generates:
To generate these, use the Debug Console and evaluate argsh.generateLaunchConfigs.
Requirements
- Bash 4.0+ — for the
DEBUGtrap andFUNCNAME/BASH_LINENO/BASH_SOURCEarrays - VSCode with the argsh extension
No additional dependencies (bashdb, etc.) are needed.
Conditional breakpoints
Right-click a breakpoint in the gutter and select Edit Breakpoint to add a condition. The breakpoint only fires when the condition evaluates to true in bash:
The condition is evaluated via bash eval at each hit — use any valid bash expression.
Watch expressions
Add watch expressions via the Watch panel in the debug sidebar. Expressions are evaluated at each breakpoint stop and their values are displayed alongside the stop event.
Modify variables at runtime
In the Variables panel, double-click a variable value to modify it. The new value is applied immediately in the running bash process via printf -v (safe assignment, no eval). This is useful for testing different code paths without restarting the script.
Subshell debugging
Commands inside $(), pipes (|), ( ... ), and { ...; } & run in subshells. The debugger fully supports debugging inside subshells:
- Breakpoints work: breakpoints fire inside subshells just like in the main shell
- Stepping works: step in/over/out work inside subshell code
- Separate thread: each subshell appears as a separate thread in VSCode's call stack panel
- No deadlocks: subshells use per-PID control FIFOs so they don't conflict with the parent process. FIFO writes are serialized via
flockto prevent interleaving. - Automatic cleanup: subshell FIFOs are cleaned up via
trap EXITwhen the subshell exits
Short-lived subshells like $(echo foo) may complete before the debugger can stop them. Breakpoints work best in longer-running subshells like background jobs ({ ...; } &) or complex pipe segments.
Limitations
set -e: The debug trap always returns 0 to avoid triggeringset -eexits.- Performance: The trap fires on every command. When not at a breakpoint and not stepping, the overhead is minimal (in-memory check only).
Extension settings
| Setting | Type | Default | Description |
|---|---|---|---|
argsh.dap.path | string | "" | Path to argsh-dap binary (auto-detected if empty) |
Troubleshooting
"Cannot find a script to debug"
Make sure you have a .sh file open in the editor, or specify program explicitly in your launch.json.
Script exits immediately
If your script uses set -e and the debug trap interferes, try adding set +e at the top of your script temporarily, or set breakpoints after the set -e line.
Breakpoints not hit
- Verify the breakpoint is on an executable line (not a comment, blank line, or
then/do/fikeyword) - Check that the file path in the breakpoint matches
BASH_SOURCE— symlinks may cause mismatches