{"record":{"id":"2cec54849ecd8ea6","repo":"charmbracelet/vhs","slug":"expected-control-character-with-args-got-litera","errorCode":null,"errorMessage":"Expected control character with args, got ${literal}","messagePattern":"Expected control character with args, got (.+?)","errorType":"validation","errorClass":"parser.Error","httpStatus":null,"severity":"error","filePath":"parser/parser.go","lineNumber":347,"sourceCode":"\t\t\tpeek.Type == token.BACKSLASH,\n\t\t\tpeek.Type == token.LEFT,\n\t\t\tpeek.Type == token.RIGHT,\n\t\t\tpeek.Type == token.UP,\n\t\t\tpeek.Type == token.DOWN,\n\t\t\tpeek.Type == token.STRING && len(peek.Literal) == 1:\n\t\t\targs = append(args, peek.Literal)\n\t\tdefault:\n\t\t\t// Key arguments with len > 1 are not valid\n\t\t\tp.errors = append(p.errors,\n\t\t\t\tNewError(p.cur, \"Not a valid modifier\"),\n\t\t\t\tNewError(p.cur, \"Invalid control argument: \"+p.cur.Literal))\n\t\t}\n\n\t\tp.nextToken()\n\t}\n\n\tif len(args) == 0 {\n\t\tp.errors = append(p.errors, NewError(p.cur, \"Expected control character with args, got \"+p.cur.Literal))\n\t}\n\n\tctrlArgs := strings.Join(args, \" \")\n\treturn Command{Type: token.CTRL, Args: ctrlArgs}\n}\n\n// parseAlt parses an alt command.\n// An alt command takes a character to type while the modifier is held down.\n//\n//\tAlt+<character>\nfunc (p *Parser) parseAlt() Command {\n\tif p.peek.Type == token.PLUS {\n\t\tp.nextToken()\n\t\tif p.peek.Type == token.STRING ||\n\t\t\tp.peek.Type == token.ENTER ||\n\t\t\tp.peek.Type == token.LEFT_BRACKET ||\n\t\t\tp.peek.Type == token.RIGHT_BRACKET ||\n\t\t\tp.peek.Type == token.TAB {","sourceCodeStart":329,"sourceCodeEnd":365,"githubUrl":"https://github.com/charmbracelet/vhs/blob/c073383b5de0b1f57bf514113029c306bc986539/parser/parser.go#L329-L365","documentation":"A Ctrl command requires at least one argument (the key to press). If parseCtrl finishes consuming tokens and args is empty, it records 'Expected control character with args, got <literal>' naming the token that started the command. Note the parser still returns a CTRL command with empty Args, so this often accompanies downstream handling of an empty control sequence.","triggerScenarios":"'Ctrl' appears with no following key token, e.g. a lone 'Ctrl' line, or 'Ctrl+' where '+' is followed by nothing valid so no argument is ever appended.","commonSituations":"Incomplete editing of a tape line (deleting the key but leaving Ctrl); truncation of the tape file; copying 'Ctrl+' from a key-combo template without filling in the key.","solutions":["Add a key argument after Ctrl, e.g. 'Ctrl+c' or 'Ctrl+Enter'.","Remove the Ctrl line entirely if no keypress was intended."],"exampleFix":"// before (.tape)\nCtrl+\n// after (.tape)\nCtrl+c","handlingStrategy":"validation","validationCode":"func ctrlHasKey(args []string) bool { return len(args) > 0 }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never leave a bare 'Ctrl' or 'Ctrl+' line in a tape","After editing key combos, re-read the line to confirm a key remains","Grep tapes for trailing '+' or lone Ctrl/Alt tokens in review"],"tags":["parser","ctrl-command","missing-argument"],"backgroundTag":"missing-required-argument","analyzedSha":"c073383b5de0b1f57bf514113029c306bc986539","analyzedAt":"2026-09-02T01:53:05.682Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-09T06:17:21.866Z"}