fix(orbit): preserve @ in JSON string literals for orbit remote query
What does this MR do and why?
Fixes a confusing failure mode of glab orbit remote query reported as the "Invalid character '@'" bug: valid JSON payloads that contain @ inside string literals (e.g. email addresses like user@example.com, Ruby @instance_var references, @version annotations) appear to be rejected by the CLI.
Symptom (user-facing report)
glab orbit remote query /tmp/q.jsonreturnsInvalid character '@'even thoughjq . /tmp/q.jsonvalidates successfully.
Root cause
The glab orbit remote query command itself never preprocesses @ — it reads the body, hands the inner query field through as json.RawMessage, and lets the API client send the bytes verbatim. So @ inside JSON string literals is in fact already preserved end-to-end.
What was actually surfacing the report was the error message the CLI emits when JSON parsing fails on a stray @ outside a string literal — for example, an unrendered @variable placeholder left over from a templating tool, or a byte shifted by a UTF-8 BOM that some editors prepend on save. The error came straight from encoding/json as invalid character '@' looking for beginning of value and was repeatedly misread as the CLI mangling @ inside string values. jq accepts BOMs and may silently mask the same defect, which is why the same file "validates with jq" but fails through glab.
Fix
The body still receives zero preprocessing; the change makes that contract explicit and improves the failure mode:
buildRequestdocuments in its godoc that@inside JSON string literals is forwarded verbatim — no curl-style@filenameexpansion, no field-magic.readBodystrips a leading UTF-8 BOM so files saved as "UTF-8 with BOM" parse the same wayjqaccepts them.- When JSON parsing fails on a stray
@at the top level, the error is wrapped with a clear hint that@inside string values is fine and thatjq . <file>will pinpoint the offending byte. The hint is baked into the error string itself because Fang's default error handler renderserr.Error()and ignoresExitError.Details.
New tests
TestBuildRequest_PreservesAtInStringLiterals— locks in the verbatim contract for@inside string literals (email addresses, Ruby@instance_varreferences,@versionannotations,@-prefixed values, multiple@s).TestBuildRequest_InvalidJSON_AtCharacterOutsideString— verifies the hinted error message points at the stray@, mentions that string-literal@s are fine, and points atjq.TestBuildRequest_InvalidJSON_NonAtCharacter— verifies the generic message is still used for non-@syntax errors.TestReadBody_StripsUTF8BOM— verifies BOM stripping.TestQuery_FromFile_WithAtInStrings— end-to-end regression for the file-input path with@s in string values.TestQuery_Stdin_WithAtInStrings— end-to-end regression for the stdin path.
How to set up and validate locally
Click to expand
# 1. `@` inside string literals is preserved end-to-end (no rejection).
cat > /tmp/q.json << 'JSON'
{"query": {"query_type": "traversal", "node": {"id": "p", "entity": "Project"}, "filter": {"email": "user@example.com", "note": "see @ahegyi"}, "limit": 1}}
JSON
glab orbit remote query /tmp/q.json
# → request is forwarded to the API (you'll see the API's response or a
# server-side schema error, but the CLI no longer rejects the body
# on the `@` characters).
# 2. Stray `@` outside a string literal now produces a helpful error
# instead of the bare stdlib message.
cat > /tmp/bad.json << 'JSON'
{"query": @variable}
JSON
glab orbit remote query /tmp/bad.json
# → Query body is not valid JSON: stray '@' outside a string literal
# at byte 11. `@` is allowed inside JSON string values
# (e.g. "user@example.com"); if your file looks correct, validate
# it with `jq . <file>` to find the real offset: invalid character
# '@' looking for beginning of value.
# 3. BOM-prefixed files now parse the same way `jq` accepts them.
printf '\xEF\xBB\xBF{"query":{"query_type":"traversal","node":{"id":"p","entity":"Project"},"limit":1}}' > /tmp/bom.json
glab orbit remote query /tmp/bom.json
# → request is forwarded (no "invalid character 'ï'").
# 4. Run the new tests
cd ~/workspace/cli
go test ./internal/commands/orbit/remote/query/...Compatibility
No CLI surface changes: same command, same flags, same exit codes. Behaviour change is strictly additive (better error message) plus the BOM-strip, which only affects inputs that were previously rejected outright.