HTTrack change report format
Description: The change report HTTrack writes after an update, field by field.

Skip to content

# Change report format specification

Run with `--changes` (`-%d`), HTTrack writes `hts-changes.json` in the project directory, next to `hts-log.txt`, describing what the crawl left new, changed, unchanged and gone compared to the previous mirror. The file is rewritten from scratch at the end of every run, and the log carries a one-line summary of the same counts.

### What "changed" means

A resource is changed when its bytes differ, not when the server merely re-sent it. HTTrack compares the payload it just received against the copy the previous run left behind: for pages it parses, the previous payload comes from the cache (the file on disk carries the mirror footer and its crawl date, so its bytes differ on every run); for everything else, the mirrored file is the payload verbatim and is compared directly.
Where no digest can be taken on either side, because the cache is disabled or the previous copy is gone, the report falls back to the transfer signal, and a server that answers 200 rather than 304 reads as changed. Keeping the cache on (the default) is what makes the report precise.

### With the cache off

`--cache=0` costs the report more than the digest of a parsed page. The mirror's file index (`hts-cache/new.lst`) is what records which files a run produced, so without it there is no previous mirror to subtract from: nothing is reported `gone`, and whether the run is a first crawl cannot be decided at all, which `first_crawl` states as `null` rather than guess. What is on disk is still compared byte for byte, so the other three lists stay meaningful, except for the pages HTTrack parses: those have no cached payload to compare against and fall back to the transfer signal.

### Fields

- `schema`: format version, currently `1`. It is bumped only on an incompatible change; new fields may appear without one.
- `generator`: the HTTrack build that wrote the file.
- `date`: when the report was written, UTC, `YYYY-MM-DDThh:mm:ssZ`.
- `first_crawl`: true when no index of a previous mirror (`hts-cache/old.lst`) was found, so there was nothing to compare against and everything is listed as new. Null when the run kept no index at all and the question cannot be answered (see above).
- `partial`: true when the report ran out of memory and lists only part of the mirror.
- `purged`: true when `--purge-old` was in effect, so the files under `gone` were also deleted from disk.
- `counts`: the size of each of the four lists.
- `new`, `changed`, `unchanged`, `gone`: the lists themselves. Every mirrored file appears in exactly one of them.

Each entry is an object:

- `url`: the absolute URL the file came from. Empty under `gone`: deletions are computed from the mirror's file index, which records paths, not URLs.
- `file`: the path relative to the mirror root, with forward slashes. This is the entry's identity: a URL and a redirect that resolve to the same local file are one entry, not two.
- `size`: the mirrored file's size in bytes, absent when the file is not on disk.
- `previous_size`: under `changed` only, the size of the copy the previous run left.

### Encoding

The file is JSON, UTF-8. URLs and local paths reach HTTrack as raw bytes and are not guaranteed to be valid UTF-8; any byte sequence that is not becomes U+FFFD (`\ufffd`), so the file always parses. Compare on `file` rather than on `url` when a mirror is known to carry legacy-charset URLs.

### Example

One captured run; `generator` and `date` will read differently in yours.

    {
      "schema": 1,
      "generator": "HTTrack Website Copier/3.49-14",
      "date": "2026-07-26T15:29:03Z",
      "first_crawl": false,
      "partial": false,
      "purged": true,
      "counts": { "new": 1, "changed": 1, "unchanged": 1, "gone": 1 },
      "new": [
        { "url": "http://example.com/d.html", "file": "example.com/d.html", "size": 280 }
      ],
      "changed": [
        { "url": "http://example.com/a.html", "file": "example.com/a.html", "size": 281, "previous_size": 273 }
      ],
      "unchanged": [
        { "url": "http://example.com/b.html", "file": "example.com/b.html", "size": 277 }
      ],
      "gone": [
        { "url": "", "file": "example.com/c.html" }
      ]
    }

### Notes

- A file listed under `gone` is only deleted when `--purge-old` is on. Left in place it drops out of the mirror's index, so it is reported once and not again.
- A resource whose local file name changed since the previous mirror (a new MIME type, say) is reported as `new` under its new name; the old name is reported as `gone` only if the file is still on disk. The two entries are not paired.
- A resource this run tried and failed to transfer also drops out of the mirror's index, but its previous copy is untouched, so it is reported `unchanged`. Under `--purge-old` that copy is deleted anyway, and the report says `gone` to match.
- A run that transfers no data at all is rolled back: HTTrack restores the previous cache generation and leaves the previous report in place, so a lost connection does not overwrite a good report with an empty one.
- Content diffs, and keeping the previous copy of a changed page, are out of scope: both change what a mirror directory contains.