charmbracelet/vhs · error · parser.Error

Modifiers must come before other characters

Error message

Modifiers must come before other characters

What it means

Ctrl in vhs accepts chains of modifier keys (Ctrl, Alt, Shift) followed by a regular key, e.g. 'Ctrl+Shift+a'. Modifiers must appear at the start of the chain; once a non-modifier key has been consumed, a later modifier is invalid. parseCtrl enforces this with inModifierChain and raises this error when a modifier keyword appears after the chain has ended.

Source

Thrown at parser/parser.go:306

// parseCtrl parses a control command.
// A control command takes one or multiples characters and/or modifiers to type while ctrl is held down.
//
//	Ctrl[+Alt][+Shift]+<char>
//	E.g:
//	Ctrl+Shift+O
//	Ctrl+Alt+Shift+P
func (p *Parser) parseCtrl() Command {
	var args []string

	inModifierChain := true
	for p.peek.Type == token.PLUS {
		p.nextToken()
		peek := p.peek

		// Get key from keywords and check if it's a valid modifier
		if k := token.Keywords[peek.Literal]; token.IsModifier(k) {
			if !inModifierChain {
				p.errors = append(p.errors, NewError(p.cur, "Modifiers must come before other characters"))
				// Clear args so the error is returned
				args = nil
				continue
			}

			args = append(args, peek.Literal)
			p.nextToken()
			continue
		}

		inModifierChain = false

		// Add key argument.
		switch {
		case peek.Type == token.ENTER,
			peek.Type == token.SPACE,
			peek.Type == token.BACKSPACE,
			peek.Type == token.MINUS,

View on GitHub (pinned to c073383b5d)

Solutions

  1. Reorder so all modifiers (Ctrl/Alt/Shift) come first, then the character key: 'Ctrl+Shift+a'.
  2. Split into separate Ctrl commands if the combination is not a modifier chain.

Example fix

// before (.tape)
Ctrl+a+Shift
// after (.tape)
Ctrl+Shift+a
Defensive patterns

Strategy: validation

Validate before calling

func isModifierFirst(keys []string) bool { i := 0; for i < len(keys) && isMod(keys[i]) { i++ }; for ; i < len(keys); i++ { if isMod(keys[i]) { return false } }; return true }
func isMod(k string) bool { switch k { case "Ctrl", "Alt", "Shift": return true }; return false }

Prevention

When it happens

Trigger: A modifier keyword (ctrl/alt/shift) appears after a regular key in a Ctrl argument list, e.g. 'Ctrl+a+Shift' or 'Ctrl+a+alt+b'.

Common situations: Reordering keys from muscle memory ('Ctrl+c+alt' instead of 'Ctrl+Alt+c'); writing key combos in the order pressed on a keyboard rather than modifier-first order.

Related errors


AI-assisted analysis of charmbracelet/vhs@c073383b5d (2026-09-02). Data as JSON: /api/errors/d83f67a4db31f616. Report an issue: GitHub.