Skip to content

fix!: close the remaining parity follow-ups with the reference CLI - #42

Open
aledbf wants to merge 37 commits into
mainfrom
parity/round3
Open

aledbf wants to merge 37 commits into
mainfrom
parity/round3

Conversation

@aledbf

@aledbf aledbf commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Summary

This round closes the parity follow-ups left open by #40 and #41, plus what turned up while checking them. Every behavior was first checked against the TS reference (v0.89.0) source and by running both CLIs. Each fix comes with a regression test that fails before the fix and passes after it: a TS-recorded ts-oracle golden, a parity matrix case or a unit test.

Breaking changes (each commit has a BREAKING CHANGE: footer and a note in docs/migration.md)

  • fix(cli)!: build and up write devcontainer-lock.json by default, as the reference does (containerFeaturesConfiguration.ts:517). The file is created when missing and updated when stale. --no-lockfile turns off both reading and writing, and --experimental-lockfile now only prints the reference's deprecation warning.
  • fix(oci)!: only localhost registries are reached over plain HTTP, as in TS getRegistryScheme. 127.0.0.1 now uses HTTPS like any other host; refer to a local plain-HTTP registry as localhost:<port>.

Output that changes for scripts (migration notes added)

  • Text log format. The text log follows the reference's two formats. When stdout is not a terminal, lines carry an ISO timestamp and Stop lines show at debug. On a terminal, lines are colored, end in \r\n, and Stop lines show only at trace. Earlier releases always wrote [N ms] text.
  • JSON results are written in the reference's key order, and &, <, >, U+2028 and U+2029 are no longer escaped (as with JSON.stringify).
  • Compose build without --image-name reports imageName as a string, as TS does.

Fixes

  • Config and templates
    • An unknown ${templateOption:x} becomes '' (TS regex port).
    • Wrong-typed legacy and customizations properties load as TS loads them.
    • forwardPorts merges with TS's parseInt and Set semantics.
    • Lockfile entries are ordered with localeCompare, so lockfiles written by TS pass --frozen-lockfile.
    • set-up reads --config without a workspace and fails on a missing config before looking for the container.
    • A compose config without workspaceFolder uses /.
  • CLI flags and errors
    • --terminal-columns/--terminal-rows are checked by presence, as yargs does, on all six commands.
    • --frozen-lockfile errors use TS's text. A missing lockfile now fails; it used to succeed silently.
    • Every command that reads the lockfile fails as TS does when it cannot read it, including V8's JSON.parse message.
  • Results
    • configFilePath gets fsPath exactly where TS reads it.
    • The file scheme is used only for --config.
    • read-configuration reports additionalMountString for git worktrees.
  • OCI
    • Cross-origin auth hosts are parsed as WHATWG URL parses them; there are 180 TS-recorded cases. :443 and the realm host now compare as TS compares them.
  • Filesystem
    • Template tarballs are extracted as node-tar extracts them: .. entries are skipped and roots stripped.
    • Writes go through os.Root, which closes a symlink escape the old extractor had.
  • Go-only --cache-key fails on an unreadable lockfile or Dockerfile instead of silently keying without it (docs/go-only-features.md).
  • An absolute build.dockerfile and its context resolve as in TS. They used to be joined onto the config folder.

Deliberate divergences (docs/DIVERGENCES.md)

  • Invalid JSONC fails to load. TS's jsonc-parser silently drops properties from it.
  • A lockfile that parses but has the wrong shape is an error. TS throws later or pins nothing.
  • A template option named like an Object.prototype property becomes ''. TS writes the function's source text.
  • Nested user objects keep sorted keys. A decoded Go map has no order.

Parity harness

  • stdout_text assert compares raw bytes, which shows escaping.
  • key_order field compares key order against TS.
  • More cases hold the feature-content-image lock: 13 more cases whose setup_cmd writes a configuration with Features now hold it, and the contract test sees them.
  • composeFeatureSets lost a mutate-lane marker, because unit tests now hold it.
  • go-tools is pinned at 5fc0868 (fix(mutate): run each test process in a private copy of the module go-tools#69). mutate runs each test process in a private copy of the module with its own TMPDIR, which removes the false survivors -j 4 reported here.

Added after review

  • The ts-oracle runs in a pinned container. The goldens recorded whatever the host's Node.js answered, so CI (Node.js 24) disagreed with a golden recorded on Node.js 22: URL parsing, IDNA and error messages change between releases. The oracle now bundles and runs in node:24-bookworm-slim, pinned by digest in versions.yaml and handed in by the Taskfile, with no network.
  • Why Node.js 24 and not 26. 24 is what CI installs the reference on, and it is the newest release the reference's lockfile builds on: its [email protected] does not compile node-pty against Node.js 26's V8.
  • Auth hosts follow Node.js 24. An all-ASCII auth host is only lower-cased, as Node.js 24 does: xn-- labels are no longer checked as Punycode. Five golden cases pin this.
  • Terminal size flags reject values int cannot hold (code scanning alert 16). On 32-bit platforms such a value was truncated and taken as given; the new test fails before the fix under GOARCH=386.

Validation

Check Result
go vet ./... on every commit pass
go test -race ./... (Docker unreachable) pass
task lint (with the go-tools bump too) pass
task vuln pass
go tool mutate -base origin/main -j 1 924 refused, 0 survived, 60 did not build
task spec:compliance pass
task parity:contract 90 matched, 0 failed
task parity:semantic 2 matched, 0 failed
task parity:runtime (Docker, full) 271 matched, 0 failed
task test:e2e pass
ts-oracle goldens refreshed against the reference 12 checked, no diff

Left for a next round

  • --override-config without a base config (configFilePath and the base for relative paths).
  • read-configuration with --container-id and --override-config but no workspace.
  • The git common-dir mount when a custom workspaceMount is set.
  • JSON log events still escape &<>.
  • TS's check that a local Feature lives inside .devcontainer/.
  • Go's simplified upgrade.

🤖 Generated with Claude Code

aledbf and others added 30 commits October 25, 2026 21:42
An unknown ${templateOption:NAME} now becomes the empty string, only a
word-character NAME (with JS whitespace around it) is matched, and an
option default is filled only for a "string" or "boolean" option whose
default is truthy, as TS replaceTemplatedValues and fetchTemplate do.
An Object.prototype name is the one documented divergence.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
A falsy "extensions", a "devPort" that customizations.vscode already
sets, or a "customizations" that is no object failed the typed decode
(exit 1, "cannot unmarshal"). TS readDevContainerConfigFile validates
no property type, so the configuration now loads and reports them as
written.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
TS mergeForwardPorts tests /localhost:\d+/ anywhere in a port string
and takes parseInt of its text from the tenth character on, so
"localhost:3000abc" is 3000, "localhost:0x10" is 16 and
"mylocalhost:1" is null. Numbers keep their fraction when they are
de-duplicated, and booleans, null and objects are kept as they are
instead of being turned into Go-formatted strings.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
TS generateLockfile sorts the features by localeCompare of their ids
and keeps each dependsOn in the order the Feature's metadata lists it;
Go sorted both by bytes, so "ghcr.io/a/b/Node" came before
"ghcr.io/a/b/alpha" and a lockfile TS wrote could fail
--frozen-lockfile here. The ids are now compared with the en-US
collator Node uses and dependsOn keeps its metadata order.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
TS doSetUp reads --config with no workspace and before it looks for
the container. ${localWorkspaceFolder} and its basename now stay as
written, as does ${containerWorkspaceFolder} unless the config sets
workspaceFolder (Go resolved them to "." and "/workspaces/."). A
missing config is "Dev container config (...) not found." and any
other load error is reported instead of being ignored. configuration
and mergedConfiguration also keep the properties the typed config has
no field for.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
TS reads devcontainer.json with jsonc-parser's error recovery, which
keeps what it could read and reports nothing: {"image": , ...} loses
"image" silently. This CLI keeps failing with the parse error; the
divergence is now documented and held by a test.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Merged forwardPorts come from decoded JSON (float64 or string) or
PortOrString values, never Go integers, so the int and int64 cases
were unreachable.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Cover parseInt's sign, white space, hexadecimal and -0 handling in the
forwardPorts merge, DependsOnKeys with a repeated or absent key, and
the dependsOn order a manifest annotation carries.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The reference picks its text log handler on process.stdout.isTTY
(spec-node/devContainers.ts createLogFrom). Piped, it writes the plain
log: `[<ISO time>] text` ending in os.EOL, raw output stamped and
stripped of escape sequences, and Stop lines only at debug and trace.
On a terminal, it writes colored `[N ms]` lines ending in \r\n, numbers
highlighted, and Stop lines only at trace (spec-utils/log.ts).

This CLI always wrote uncolored `[N ms] text\n` and showed a Stop line
whenever its level passed, matching neither. The parity normalizer
scrubbed both timestamp shapes to the same token, which hid it; it now
scrubs only the ISO shape.

--log-file and --terminal-log-file still receive the log without color
or \r, as documented. The goldens in internal/log/testdata/ts-oracle
record the reference handlers' output for each event.

Scripts that parsed `[N ms]` from piped stderr must read the ISO
timestamp instead (docs/migration.md).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
yargs .implies() fails when a flag is given without the flag it implies,
whatever their values (validation.js keyExists tests hasOwnProperty).
The reference declares --terminal-columns and --terminal-rows to imply
each other on up, set-up, run-user-commands, read-configuration,
outdated and exec (devContainersSpecCLI.ts).

This CLI tested the values (> 0), so `--terminal-rows 0` alone passed
where the reference exits 1 with "Implications failed", and outdated
did not check the pair at all. The flags now record whether they were
given, parsing their value as before.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Without --image-name, the reference's doBuild sets
imageNameResult = originalImageName for a Docker Compose configuration,
so the result reads "imageName":"<name>", a string; with --image-name it
is the array of the given names (devContainersSpecCLI.ts). This CLI
always wrote an array.

A consumer that read the compose case as an array must read a string
now, as it already had to with the reference (docs/migration.md).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The reference normalizes --allow-cross-origin-auth-host authorities with
new URL(`https://${authority}`) and compares registry and realm hosts by
their WHATWG `host`. net/url kept an explicit :443, refused forms Node
accepts (a trailing "/", "\", dot segments, a bare "?" or "#", empty
userinfo, tabs) and did no IDNA, IPv4 or IPv6 normalization, so a
mapping TS trusts could fail to parse or never match the realm.

Port the WHATWG host rules for http(s) URLs and use them both to
normalize the configured entries and to key the registry and realm
lookups, so default ports drop on both sides. The ts-oracle golden pins
parseCrossOriginAuthHosts and isAllowedTokenServiceRealm.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The reference picks the registry scheme with getRegistryScheme: plain
HTTP when the WHATWG hostname is localhost, HTTPS otherwise. The Go
client also sent 127.0.0.1 registries over plain HTTP, although
Ref.Scheme, and with it the read-configuration featureRef.scheme, said
https, and the auth policy's localhost-only HTTP realm rule assumed the
reference's split.

Derive PlainHTTP from Ref.Scheme. The hermetic registry fixtures now
address httptest servers as localhost:<port>, as the parity matrix
already does for the reference.

BREAKING CHANGE: a plain-HTTP registry referred to as 127.0.0.1:<port>
is now reached over HTTPS; refer to it as localhost:<port>.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
read-configuration --cache-key ignored every ReadLockfile error, so a
malformed devcontainer-lock.json keyed as if no lockfile were committed
and the key stopped tracking the pinned digests without a word. The
reference's readLockfile rethrows everything but ENOENT, failing each
command that reads the lockfile.

Fail the key with the lockfile path and the parse or read error, and
report it as read-configuration reports its other runtime failures: the
error result on stdout, exit code 1. Without --cache-key the command
still does not read the lockfile, as the reference does not.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
ExtractTarGz re-rooted every entry under the destination before its
path-traversal guard ran, so the guard never fired: "../up.txt" and
"a/../mid.txt" were written as up.txt and mid.txt, and Windows roots
stayed in the name. Writes also followed a symlink already inside the
destination, so an entry beneath it landed outside.

Treat entry paths as node-tar, which extracts the reference's blobs,
does: skip an entry with a ".." segment (either separator) and strip
'/', '\' and drive-letter roots. Write through an os.Root on the
destination, so a path that would escape it through a symlink fails
the extraction, and report directory and chmod errors instead of
dropping them.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
A Docker Compose configuration without workspaceFolder now works in `/`,
as the reference does (getWorkspaceConfiguration returns
getRemoteWorkspaceFolder, `config.workspaceFolder || '/'`). Go derived
/workspaces/<basename>, a folder the compose service never mounts, for
read-configuration's workspace, ${containerWorkspaceFolder}, up's
remoteWorkspaceFolder, the lifecycle commands' cwd and exec's cwd.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
--frozen-lockfile without a lockfile now fails with "Lockfile does not
exist." instead of passing unchecked, and both frozen failures report
the reference's envelope: the lockfile error as the message and the
command's "An error occurred building/setting up the container." as the
description (lockfile.ts writeLockfile, wrapped as the originalError of
the command's ContainerError). Go wrote "lockfile: lockfile does not
match" into both fields.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The results of up, build, set-up, run-user-commands and
read-configuration now list their properties as the reference's
JSON.stringify does, in the insertion order of the object literals that
build them: outcome first, read-configuration's configuration,
workspace, featuresConfiguration, mergedConfiguration, then
ociAuthDiagnostics. A configuration keeps its document's top-level order
with configFilePath last, and a merged configuration the order
mergeConfiguration produces. Go wrote every object with sorted keys,
which the parity matrix could not see because it sorts keys; its new
key_order field compares the order against the reference.

Objects nested deeper still have sorted keys, a decoded Go map keeping
no order; docs/DIVERGENCES.md records it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The reference's readLockfile rethrows every failure but ENOENT: a file
that cannot be read fails with the Node.js fs error ("EISDIR: illegal
operation on a directory, read", "EACCES: permission denied, open
'<path>'") and one that is not JSON with V8's JSON.parse SyntaxError
("Unexpected token 'o', "not json" is not valid JSON", "Expected ','
or '}' after property value in JSON at position 72 (line 2 column 1)").
ReadLockfile returned Go's own decoding error, and its callers dropped
it.

Return a *LockfileError carrying the class, message and fs properties
TS reports. The V8 parser's error reporting is ported over the UTF-16
text Buffer.toString decodes (WHATWG UTF-8); the empty-marker check
trims as String.prototype.trim does, and null, false, 0 and "" are no
lockfile, as TS reads the parsed value through `?.`. JSON shaped unlike
a lockfile stays an error (documented in DIVERGENCES.md). A ts-oracle
golden runs the reference's readLockfile over the same files.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
outdated dropped every ReadLockfile error and reported versions as if
no lockfile were committed. In the reference, loadVersionInfo calls
readLockfile, which rethrows all but ENOENT, and outdated's catch logs
the error's stack (output.write, debug level) and exits 1 with nothing
on stdout.

Do the same. A unit test and the contract parity case
outdated.malformed-lockfile-error hold it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
features resolve-dependencies dropped every ReadLockfile error and
printed the graph as if no lockfile were committed. In the reference,
readLockfile rethrows all but ENOENT and runAsyncHandler reports the
uncaught error with console.error and exit code 1: the SyntaxError's
stack, or for a file system error Node's "[Error: ...] { errno, code,
syscall, path }".

Print the same on stderr (frames left out) and exit 1. A unit test and
the contract parity case
features.resolve-dependencies-malformed-lockfile-error hold it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
extendImageWithFeatures dropped every ReadLockfile error, so up and
build went on unpinned when devcontainer-lock.json was malformed or
unreadable. The reference's generateFeaturesConfig calls readLockfile,
which rethrows all but ENOENT, and up and build wrap the error in a
ContainerError: message the error's own, description "An error
occurred setting up/building the container.", exit code 1.
--no-lockfile still skips the file.

Return the error, and let commandErrorOutput report an error carrying
the reference's own message (errors.ReferenceError, which LockfileError
implements) the way TS reports any error it wraps. Unit tests and the
runtime parity cases build.malformed-lockfile-error and
up.malformed-lockfile-error hold it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
composeFeatureSets dropped every ReadLockfile error, so a Compose
configuration's Features were resolved unpinned when
devcontainer-lock.json was malformed or unreadable. The reference
rethrows readLockfile's error there too, and up and build report it as
their error result: message the error's own, description "An error
occurred setting up/building the container.", exit code 1.
--no-lockfile still skips the file.

Return the error. A unit test and the runtime parity cases
build.compose-malformed-lockfile-error and
up.compose-malformed-lockfile-error hold it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
read-configuration --cache-key folded the Dockerfile of a
Dockerfile-based configuration into the key only when it could read
it, so a missing or unreadable Dockerfile keyed as if the configuration
had none and the key stopped tracking the image's recipe without a
word.

Fail the key with "read Dockerfile: <error>", the file build reads and
would fail on, reported as read-configuration's other runtime failures
are (error result on stdout, exit code 1). Without --cache-key the
command still does not read the Dockerfile, as the reference does not.
Documented in docs/go-only-features.md.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The reference writes every command result with JSON.stringify, which
writes &, <, > and U+2028/U+2029 in strings as they are. encoding/json
escaped them, so a result holding `a && b <x>` was not byte-compatible
with the reference's. Results and error results now undo those escapes
after encoding, which also covers strings nested json.Marshaler
implementations write.

The parity matrix gains a stdout_text assert that compares stdout as
text, since stdout_normalized parses the JSON and cannot see escaping.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
vscode-uri's toJSON writes fsPath only once something read that getter,
so the reference's up writes it in configFilePath when its code path
read it: without --id-label (findContainerAndIdLabels), for a --config
file read without --override-config (readDocument), and when Features
are processed with the lockfile on (getLockfilePath). Go never wrote it
in up. read-configuration and set-up always read it and already wrote
it; both now build the URI through the same helper.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The reference makes a file URI of --config, and of --override-config
only when there is no workspace; a configuration found in the
workspace keeps the vscode-fileHost scheme whatever else is given. Go
wrote the file scheme whenever --override-config was given (up and
read-configuration) or --container-id was (read-configuration). The
unit tests that held the old schemes now hold the reference's, which
both CLIs were run to confirm.

Without a workspace the override is the document up reads, so its
URI also has fsPath.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
With --mount-git-worktree-common-dir and a worktree whose .git file
names its gitdir by a relative path, the reference's workspace
configuration carries additionalMountString, the common dir's bind
mount, and read-configuration writes it after workspaceFolder and
workspaceMount. Go mounted it in up but left it out of the result.
The workspace object is now also written in the reference's key order.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The reference CLI (v0.89.0) writes devcontainer-lock.json on every build
and up that has Features unless --no-lockfile is given
(containerFeaturesConfiguration.ts generateFeaturesConfig ->
lockfile.ts writeLockfile, which creates a missing file). build and up,
single-container and Compose, only wrote it with --experimental-lockfile
or --frozen-lockfile. They now create or update it by default;
--no-lockfile disables reading and writing it, and --frozen-lockfile is
unchanged.

--experimental-lockfile and --experimental-frozen-lockfile now print
the reference's deprecation warnings on stderr. upgrade truncates the
lockfile before writing it, as upgradeCommand.ts does, so a lockfile
that differs only in formatting is rewritten in canonical form.

BREAKING CHANGE: build and up create devcontainer-lock.json (or
.devcontainer-lock.json next to .devcontainer.json) in projects that use
Features. Commit it to pin the Features, or pass --no-lockfile to
neither read nor write it. See docs/migration.md.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…ence does

build and up joined build.dockerfile onto the configuration's folder,
so an absolute path became '<config dir>/<absolute path>' and the build
failed to read it; --cache-key did the same. The reference resolves it
against the configuration's folder, which keeps an absolute path
(configuration.ts getDockerfilePath). Without build.context the context
is the Dockerfile's folder (spec-node/utils.ts getDockerContextPath),
where Go used the configuration's folder, and an absolute context was
mangled the same way.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
aledbf and others added 4 commits October 25, 2026 23:56
…ror marker

commandErrorOutput recognised the reference's own message two ways: a
loop over the frozen-lockfile sentinels and the ReferenceError marker
the lockfile read errors carry. The sentinels now carry the marker, so
one check reports every such error as its message under
"<description>.", with the same output as before.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
TestParityMatrixLocksTheFeatureContentImage read Features only from
fixture configurations, so 13 cases whose setup_cmd writes a
configuration with Features and builds it without BuildKit ran
without the feature-content-image lock and could take each other's
Features through dev_container_feature_content_temp. The check now
also reads the configuration a setup_cmd writes, and those cases hold
the lock.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
composeFeatureSets carried a mutate-lane marker for the runtime parity
case up.compose-config-file-fspath-features-success, which kept every
line of it out of mutation testing although unit tests hold all of them
but the lockfileRead flag. TestComposeFeatureSets now checks the flag
(set only when Features are processed with the lockfile on), and the
marker is gone.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
go-tools 5fc0868 runs each mutate test process in a private copy of the
module with a temporary directory of its own (spin-stack/go-tools#69).
Here, mutants of internal/cli wrote Dockerfile-with-features and a
timestamped cache folder into shared places, so `mutate -j 4` reported
survivors that a serial run refused, a different set each run. The pin
also brings testquality's check for tests that sleep a second or more;
it finds none here.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Comment thread internal/cli/validate.go Fixed
aledbf and others added 3 commits October 26, 2026 00:29
givenInt parsed --terminal-columns/--terminal-rows as int64 and
converted to int, so on a 32-bit platform 2147483648 became
-2147483648 and was taken as given (code scanning: incorrect
conversion between integer types). It now parses at int's size and
fails on such a value. The new case in TestGivenIntParsesLikeIntVar
fails before the fix under GOARCH=386; on 64-bit platforms nothing
changes.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Node.js 24's URL parser only lower-cases a host that is all ASCII; it no
longer checks its xn-- labels as Punycode, so xn--a, xn-- and xn--.example
are hosts where Node.js 22 rejected them. The ts-oracle golden recorded
Node.js 22's answers on one machine while CI ran Node.js 24, so the
oracle gate failed there. domainToASCII now lower-cases any ASCII domain,
and five more golden cases pin the rule.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The goldens recorded what the host's Node.js answered, so the same
reference functions gave other answers on another machine (URL parsing,
IDNA and Node.js error messages change between releases). The oracle
now bundles and runs in node:24-bookworm-slim, pinned by digest in
versions.yaml and handed in by the Taskfile, with no network: Node.js 24
is what CI installs and runs the reference on, and the newest release
the reference's lockfile builds on (its nan 2.25.0 does not compile
node-pty against Node.js 26). A bump that changes an answer fails the
oracle gate until the goldens are refreshed. The readLockfile oracle
writes its temporary folder as <dir>, so a message naming the file
stays the same from run to run. TestTSOracleGoldensMatchTheReference
skips without Docker or without TS_ORACLE_IMAGE, which task
parity:contract sets.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants