restic/restic · error

double-quoted string not terminated

Error message

double-quoted string not terminated

What it means

Companion of the single-quote check in SplitShellStrings: after end of input the tokenizer still has a double quote open, so the string cannot be split into well-formed arguments and the parse fails.

Source

Thrown at internal/backend/shell_split.go:68

	for i, r := range data {
		if s.isSplitChar(r) {
			if fieldStart >= 0 {
				strs = append(strs, data[fieldStart:i])
				fieldStart = -1
			}
		} else if fieldStart == -1 {
			fieldStart = i
		}
	}
	if fieldStart >= 0 { // Last field might end at EOF.
		strs = append(strs, data[fieldStart:])
	}

	switch s.quote {
	case '\'':
		return nil, errors.New("single-quoted string not terminated")
	case '"':
		return nil, errors.New("double-quoted string not terminated")
	}

	if len(strs) == 0 {
		return nil, errors.New("command string is empty")
	}

	return strs, nil
}

View on GitHub (pinned to a80be1478a)

Solutions

  1. Close the double quote: ensure every opening '"' has a matching '"'.
  2. Prefer single quotes at the outer layer and double quotes inside, or vice versa, to halve the nesting.
  3. Paste the value into 'printf %s' / a validator to confirm the character counts match.

Example fix

# before
restic -o sftp.command='ssh -o ProxyCommand="nc jump 22' -r sftp:host:/repo snapshots
# -> double-quoted string not terminated

# after
restic -o sftp.command='ssh -o ProxyCommand="nc jump 22"' -r sftp:host:/repo snapshots
Defensive patterns

Strategy: validation

Validate before calling

func doubleQuotesBalanced(s string) bool {
	var inSingle bool
	count := 0
	for _, r := range s {
		if r == '\'' { inSingle = !inSingle }
		if r == '"' && !inSingle { count++ }
	}
	return count%2 == 0
}

Try / catch

args, err := backend.SplitShellStrings(cmd)
if err != nil {
	return nil, fmt.Errorf("invalid sftp.command %q: %w", cmd, err) // covers unterminated double quotes
}

Prevention

When it happens

Trigger: sftp.command values such as 'ssh -o ProxyCommand="nc host 22' (opening double quote never closed), or values mangled by YAML/JSON/config escaping that strips one quote of a pair.

Common situations: Nested quoting through multiple layers (shell -> restic option -> ssh argument); CI pipelines where YAML folding removes quotes; editing options in a UI that trims trailing quote characters.

Related errors


AI-assisted analysis of restic/restic@a80be1478a (2026-08-15). Data as JSON: /api/errors/dc09d517e9914e99. Report an issue: GitHub.