charmbracelet/vhs · error · parser.Error
Invalid command: ${literal}
Error message
Invalid command: ${literal} What it means
In vhs (charmbracelet/vhs), a .tape file is a sequence of commands (Type, Copy, Paste, Env, Wait, Sleep, etc.). parseCommand dispatches on the current token's type, and when it sees a token that maps to no known command it records this error and returns an ILLEGAL command so parsing can continue. It means the parser encountered a word at a command position that is not one of vhs's built-in commands.
Source
Thrown at parser/parser.go:189
return []Command{p.parseHide()}
case token.REQUIRE:
return []Command{p.parseRequire()}
case token.SHOW:
return []Command{p.parseShow()}
case token.WAIT:
return []Command{p.parseWait()}
case token.SOURCE:
return p.parseSource()
case token.SCREENSHOT:
return []Command{p.parseScreenshot()}
case token.COPY:
return []Command{p.parseCopy()}
case token.PASTE:
return []Command{p.parsePaste()}
case token.ENV:
return []Command{p.parseEnv()}
default:
p.errors = append(p.errors, NewError(p.cur, "Invalid command: "+p.cur.Literal))
return []Command{{Type: token.ILLEGAL}}
}
}
func (p *Parser) parseWait() Command {
cmd := Command{Type: token.WAIT}
if p.peek.Type == token.PLUS {
p.nextToken()
if p.peek.Type != token.STRING || (p.peek.Literal != "Line" && p.peek.Literal != "Screen") {
p.errors = append(p.errors, NewError(p.peek, "Wait+ expects Line or Screen"))
return cmd
}
cmd.Args = p.peek.Literal
p.nextToken()
} else {
cmd.Args = "Line"
}View on GitHub (pinned to c073383b5d)
Solutions
- Check the command name spelling against the vhs command list (Type, Enter, Space, Ctrl, Alt, Shift, Sleep, Wait, Set, Output, Hide, Show, Screenshot, Copy, Paste, Env, Tab, Backspace, Delete, Down, Up, etc.).
- If the line is meant to be a comment, prefix it with '#' or wrap it in a Comment block.
- If it is meant to be a shell command, prefix it with '$' (e.g. '$ echo hello').
- Run 'vhs' with your tape via a recent version; update vhs if the command exists in newer releases.
Example fix
// before (.tape) Tyep "hello" // after (.tape) Type "hello"
Defensive patterns
Strategy: validation
Validate before calling
var validCommands = map[string]bool{"Type":true,"Enter":true,"Space":true,"Tab":true,"Backspace":true,"Delete":true,"Up":true,"Down":true,"Left":true,"Right":true,"Sleep":true,"Wait":true,"Set":true,"Output":true,"Hide":true,"Show":true,"Screenshot":true,"Copy":true,"Paste":true,"Env":true,"Ctrl":true,"Alt":true,"Shift":true,"Source":true,"Require":true}
func isValidTapeCommand(word string) bool { return validCommands[word] } Type guard
func isKnownCommand(tok string) bool { _, ok := token.Keywords[tok]; return ok } Prevention
- Spell-check command names against the vhs README command table
- Prefix commentary lines with '#' and shell commands with '$'
- Pin and update your vhs version; check release notes for renamed commands
- Run 'vhs yourfile.tape' early in CI to catch parse errors before recording
When it happens
Trigger: The first word of a line in a .tape file is not a registered token/command, e.g. a typo like 'Tyep foo' or 'Prtscr', or an unsupported/renamed command, or stray text pasted into the tape file.
Common situations: Typos in command names; copying commands from old vhs examples that were renamed between versions; leaving prose or shell text directly in a .tape file instead of inside Output/Comment or $ shell commands; using commands from a fork or newer vhs release than the installed binary.
Related errors
- Wait+ expects Line or Screen
- Wait expects positive duration
- Invalid regular expression '${literal}': ${err}
- Expected time after ${literal}
- Modifiers must come before other characters
AI-assisted analysis of charmbracelet/vhs@c073383b5d (2026-09-02).
Data as JSON: /api/errors/2ccdafee47dac83a.
Report an issue: GitHub.