Skip to content

Adding pre_command_substitution and post_expansion_pre_dispatch hooks#935

Draft
k2tomasz wants to merge 18 commits intoreubeno:mainfrom
k2tomasz:filter
Draft

Adding pre_command_substitution and post_expansion_pre_dispatch hooks#935
k2tomasz wants to merge 18 commits intoreubeno:mainfrom
k2tomasz:filter

Conversation

@k2tomasz
Copy link
Copy Markdown

@k2tomasz k2tomasz commented Jan 16, 2026

Two hooks were added to the ShellExtensions trait:

  1. post_expansion_pre_dispatch_simple_command - A post-expansion policy hook that:
  • Fires after word expansion and after the pre_exec_simple_command filter chain
  • Fires before dispatch to builtin/function/external execution
  • Receives &SimpleCommand and &CommandContext (with pipeline info, redirections, dispatch target, raw command)
  • Returns a PolicyDecision (Continue or Return early)
  • Is read-only — cannot modify the command, only allow/deny execution
  1. pre_command_substitution - A pre-command-substitution policy hook that:
  • Fires before executing $(...) or backtick substitutions during expansion
  • Receives CommandSubstitutionContext (raw body text, syntax type)
  • Returns CommandSubstitutionDecision (Allow, ReplaceOutput, or Error)
  • Allows blocking or replacing command substitution output to prevent side effects during expansion

Additionally, a supporting method was added:

needs_command_context() - An opt-in method (defaults to false) that extensions override to signal they want the post-expansion hook invoked. This enables lazy context construction for near-zero overhead when unused.

All of these are gated behind the experimental-filters feature flag.

How would this work?

flowchart TD
    A[Parse Command] --> B[Expand Words]

    B --> C{Command Substitution?}
    C -->|Yes| D[pre_command_substitution]
    D -->|Allow| E[Execute Substitution Body]
    D -->|Replace Output| F[Use Replacement Output]
    D -->|Error| X[Abort Expansion]
    C -->|No| G[Continue Expansion]

    E --> H[Build SimpleCommand]
    F --> H
    G --> H

    H --> I[Run pre_exec_simple_command filter chain]
    I --> J{Any extension needs context?}
    J -->|No| K[Dispatch/Execute]
    J -->|Yes| L["Resolve DispatchTarget (cheap)"]
    L --> M[Build CommandContext]
    M --> N["post_expansion_pre_dispatch_simple_command (policy)"]
    N -->|Continue| K
    N -->|Return early| O[Skip dispatch]
    K --> P[post_exec_simple_command]
    O --> P
Loading

reubeno and others added 15 commits December 15, 2025 08:00
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Add PolicyDecision, CommandSubstitutionContext, SubstitutionSyntax,
and CommandSubstitutionDecision types for the experimental-filters
feature. Also add Debug derive to ExecutionExitCode.

These types support the post-expansion policy hook (task 1.2) and
pre-command-substitution hook per the design spec.
… handling

- Add detailed module documentation explaining command execution and policy hook types
- Document post-expansion policy hook system for security/policy enforcement
- Add pre-command-substitution hook types and context structures
- Document performance characteristics and feature gating of policy hooks
- Implement `convert_redirect_to_info()` function to convert AST redirections to structured format
- Replace `.unwrap()` calls with proper error handling using `if let Ok` pattern in custom-extensions example
- Improve code robustness by gracefully handling mutex poisoning in extension hooks
- Add comprehensive documentation for redirection conversion including arguments, returns, and feature flags
- Update inline documentation with backtick formatting for code references
@k2tomasz k2tomasz changed the title Filter Adding pre_command_substitution and post_expansion_pre_dispatch hooks Jan 16, 2026
@k2tomasz
Copy link
Copy Markdown
Author

@reubeno let me know if you like having hooks like these and I can improve/clean this up and then create a PR for main once your filters PR gets merged.

feat(expansion): handle errors marked as reported by extensions

feat(shell): suppress duplicate error messages for reported errors

test(suppression): add tests for error suppression behavior
@reubeno
Copy link
Copy Markdown
Owner

reubeno commented Jan 17, 2026

Thanks for proposing this!

I like the idea of more hooks, provided we can keep the cost/complexity managed. My real goal is to remove the feature flag once we make Shell generic over extensions so the no-hook case doesn't incur any overhead.

A command substitution hook definitely makes sense. I'll need to skim through your changes to understand the pipeline and redirection context getting built up. Let me get back to you on that.

@reubeno reubeno added the area: extensibility Related to shell/library extensibility label Jan 17, 2026
@reubeno
Copy link
Copy Markdown
Owner

reubeno commented Jan 19, 2026

As an update -- I've merged in #941, which introduces ShellExtensions and converts Shell => Shell<SE: ShellExtensions>. I'd next like to adapt some of the core filter functionality from #838 and perhaps introduce just 1 hook point. That would then allow for rebasing some of your work on top of that and getting it properly reviewed. I'll ping back here when that's ready...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: extensibility Related to shell/library extensibility

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants