feat(alias): forward positional args to templates via {{ args }}#2280
Merged
feat(alias): forward positional args to templates via {{ args }}#2280
Conversation
worktrunk-bot
approved these changes
Apr 18, 2026
Non-flag tokens after the alias name are collected into a new
positional_args field on AliasOptions and exposed in templates as a
ShellArgs sequence. Bare {{ args }} renders as a space-joined,
shell-escaped string ready to splice into a command line; indexing,
iteration, and length behave like any minijinja sequence.
ShellArgs implements minijinja's Object trait with ObjectRepr::Seq; the
shell-escape formatter in setup_template_env detects it via
downcast_object_ref and passes its Display through unmodified so the
joined result isn't double-escaped. Args flow through the existing
HashMap<String, String> context as a JSON-encoded list (ALIAS_ARGS_KEY)
that expand_template rehydrates into a ShellArgs object.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drop the redundant shell-safety restatement (already in the section intro), the flag-interleave example (obvious once positionals work), and the niche stdin JSON note. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
b92b12f to
bb096cf
Compare
worktrunk-bot
approved these changes
Apr 18, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Non-flag tokens after an alias name are forwarded to the template as
{{ args }}— a space-joined, shell-escaped sequence.wt s some-branchwiths = "wt switch {{ args }}"expands towt switch some-branch.Before:
wt s some-brancherrored withUnexpected argument 'some-branch' for alias 's'.Why
Users want to pass arguments through to aliased commands —
wt s foo→wt switch foo— without configuring--var foo=…for every possible parameter. Positional args are the natural fit.Behavior
{{ args }}is a minijinja sequence. All four access patterns work:{{ args }}{{ args[0] }}{{ args | length }}{% for a in args %}Each element is individually shell-escaped, so
wt run 'a b' 'c;d'splices in as'a b' 'c;d'and can't inject shell syntax. Positionals interleave freely with flags —wt deploy foo --dry-run barcollects["foo", "bar"]intoargs.--dry-run,--yes,-y,--var KEY=VALUE, and--KEY=VALUEparsing are unchanged.Navigating the diff
src/config/expansion.rs— newShellArgsstruct wrapsVec<String>and implements minijinja'sObjecttrait withObjectRepr::Seq. Itsrender()writesshell_escape::unix::escapeof each element, space-joined. The shell-escape formatter insetup_template_envdetectsShellArgsviadowncast_object_refand passes itsDisplaythrough unmodified — so bare{{ args }}isn't double-escaped by the generic per-value escape. Iteration and indexing yield plainValue::from(String)that still flow through the generic formatter. NewALIAS_ARGS_KEYconstant ("args") is the reserved context key carrying the JSON-encoded list.src/commands/alias.rs—AliasOptionsgains apositional_args: Vec<String>field.AliasOptions::parsecollects non-flag tokens into it instead of erroring.run_aliasinserts the JSON-encoded list intocontext_mapunderALIAS_ARGS_KEYright afterbuild_hook_context. The sole special-case lives inexpand_template, which rehydrates the key as aShellArgsobject; all other call sites (hooks, for-each, eval) are untouched and never see the key.validate_template— injects an emptyShellArgsso templates referencing{{ args }}pass pre-flight validation.argsis added toTEMPLATE_VARS.docs/content/extending.md— new "Forwarding positional arguments" subsection under Aliases with a shell-safety guarantee and access-pattern examples.Tests
3233 tests pass, lints clean. New:
src/commands/alias.rs—test_parsesnapshots extended with positional cases including interleaved flags and metacharacter-laden args.test_parse_errorsno longer expects "Unexpected argument".src/config/expansion.rs—test_expand_template_args_sequencecovers indexing, iteration, and length.test_expand_template_args_emptyconfirms empty renders to empty string.test_expand_template_args_shell_metachar_safetyasserts the exact output for['; rm -rf /', '$(whoami)', "a'b"].test_validate_template_validnow covers{{ args }},{{ args | length }}, and iteration.tests/integration_tests/step_alias.rs— end-to-end snapshots forwt step <alias> positionals,wt <alias> some-branch --dry-run, empty positionals, and sequence-style access.Do-nots
--dry-run,--yes,--var, or--KEY=VALUEparsing — the broader flag-handling question (whether alias-level flags should move pre-name) is being designed separately.KEY=valuebare-token parsing for vars — deferred.wt step <alias>semantics beyond inheriting positional support via sharedAliasOptions::parse.🤖 Generated with Claude Code