github unkn0wn-root/resterm v1.14.0

3 hours ago

v1.14.0

Fixed history backups that could delete an unrelated file and compaction that reported success without shrinking the database.

If you use the headless Go API, check the breaking changes below before upgrading.

History permissions

On macOS and Linux, history.db and its -wal and -shm files are created with 0600 permissions. New history and runner state directories use 0700. History includes response bodies, so these files should only be readable by their owner.

Resterm removes group and other permissions from existing files when it opens them. It leaves the owner's permissions alone, including files you've made read-only.

History exports, backups, and runner state saved with --persist-globals or --persist-auth get the same owner-only permissions. Runner state is written to a temporary file first, then renamed into place to avoid partial writes after a crash.

Backups

resterm history backup writes through SQLite instead of deleting the destination first. This fixes backups made over a database another Resterm process has open, including targets with WAL files left after a crash. It waits up to 5 seconds if another process is writing to the target.

The destination gets owner-only permissions before any history is copied, even if the old backup had broader permissions.

A non-SQLite file or a directory is refused and left alone. For example, trying to back up over a text file gives:

error[history]: backup history
╰─> cannot replace notes.txt because it is not a readable SQLite database. Remove it or pick another path

Previous versions deleted that file and replaced it with the backup.

The command also refuses to overwrite the live history file through a symlink or, on macOS, a differently cased name such as HISTORY.db. In 1.13.3, that case mismatch could delete and replace the live database.

Compact

resterm history compact shrinks the database before returning. Previously, the file stayed the same size until Resterm exited, which made the reported sizes wrong.

If another process is reading the database and prevents compaction from finishing, the command exits with 1:

error[history]: checkpoint history db
╰─> another process is using the history db, so its file did not shrink. Try again later

More in the history command docs.

Trace start times

Trace summaries written to traces/ with --artifact-dir include a start timestamp for each phase:

{
  "kind": "connect",
  "start": "2026-10-09T17:34:11.015+02:00",
  "duration": 20000000,
  "meta": {}
}

The timestamps show phase order and gaps between phases. They're saved in history too. Older entries still display phases back to back.

See Artifacts and persisted state.

Workflow and profile errors

When a workflow or profile fails because of a child step or run, its JSON failure includes the child's chain and frames. You can find the source of the error in the parent result. This applies to both resterm run --format json and the headless API.

Headless Go API

The headless package shares more of its reporting behavior with resterm run:

  • Result.EffectiveTarget and Step.EffectiveTarget contain the URL reached after variable expansion and redirects.
  • ErrorDetail and ScriptErrorDetail include the CLI error text with file, line and column.
  • Result.Warnings includes script warnings.
  • Text and JUnit output include error class and file location.
  • ParseFormat returns ErrUnknownFormat for unrecognized names.

You can also read report JSON back into a headless.Report, including output from resterm run --format json:

// data holds the output of resterm run --format json
var rep headless.Report
if err := json.Unmarshal(data, &rep); err != nil {
	log.Fatal(err)
}
fmt.Println(rep.Failed, rep.ExitCode(headless.ExitCodeDetailed))

Decoded durations have millisecond precision. ErrorDetail and ScriptErrorDetail remain empty because they aren't in the JSON.

Breaking changes

Check these if you call the headless API from Go:

  1. A nil context, report or writer, or an unknown format, returns a UsageError. Use errors.Is(err, headless.ErrNilContext); a direct == comparison no longer matches.

  2. Failed steps, profile runs outside warmup, and streams make their parent result fail. Failed(), HasFailures() and both exit code modes use the same rule, including for reports you construct yourself. HasFailures() is true exactly when the exit code is nonzero.

  3. Marshaling individual report types such as Test, Trace or Failure uses the same field names and millisecond durations as the full report:

    1.13.3  {"name":"status is 200","passed":true,"elapsed":1500000}
    1.14.0  {"name":"status is 200","passed":true,"elapsedMs":1}
    

    A struct embedding one of these types also inherits its MarshalJSON method.

See Read the report and Output and exit codes.

Other fixes

  • History entries created in the same microsecond no longer overwrite each other. This caused lost entries on macOS due to its clock precision.
  • Workspace names containing ? no longer send runner history to the wrong file. For example, api?v2 previously wrote to <config-dir>/runner/api, which was readable by everyone. History goes to the workspace's state folder; the old file isn't moved.
  • Requests that end before their first trace phase finishes, including canceled requests, keep their trace budget and any breach in history.
  • Closing history while an entry is being written no longer crashes Resterm.

Upgrading from 1.13.2 or earlier

1.13.3 changed parsing for quoted options:

  • A whole option in quotes, such as # @settings "timeout=1s", is treated as text and produces a warning.
  • @auth rejects a quoted field where it expects an option.
  • A trace budget written with ==, such as dns==50ms, produces a warning instead of being silently ignored.

Don't miss a new resterm release

NewReleases is sending notifications on new releases.