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

  1. 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.).
  2. If the line is meant to be a comment, prefix it with '#' or wrap it in a Comment block.
  3. If it is meant to be a shell command, prefix it with '$' (e.g. '$ echo hello').
  4. 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

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


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