Skip to content

Pin the CLI's tsx dependency to an exact version #538

Closed

Description

@theCodeDrift

@taskless/cli depends on tsx as a range ("tsx": "^4.21.0"). It isn't a build-time tool for us: src/rules/runtime/invoke.ts resolves the installed tsx at runtime and runs every runtime rule's check.ts through it. So the tsx version that runs those rules is whatever npm resolves on the day of the install, not the one we built and tested against.

Seen on 2026-10-19: npm install --ignore-scripts @taskless/cli@0.13.1 into an empty directory resolved:

This repo's lockfile What a fresh install got
tsx 4.21.0 4.23.15
esbuild 0.27.3 0.28.x (tsx 4.23 asks for ~0.28.0)
get-tsconfig present (a dependency of tsx 4.21) absent (tsx 4.23 no longer depends on it)

Two installs of the same CLI version can transpile and run rule code with different tsx and esbuild versions. Runtime rule behavior then depends on when someone installed the CLI, not on the version recorded in .taskless/taskless.json.

Ask: pin tsx to an exact version in packages/cli/package.json, and bump it on purpose.

Pinning tsx doesn't pin what tsx itself depends on: its esbuild range (~0.27.0 in 4.21.0) still floats within patch releases. If we want the whole tree fixed per CLI version, publishing an npm-shrinkwrap.json with the package does that, and npm honors it when the package is installed. That's a bigger change and can be a follow-up.

Context: the project launcher (#537) and a planned follow-up that installs the recorded CLI version into a git-ignored .taskless/.cache/ make "the version in taskless.json is what runs" the premise. A floating runtime dependency undercuts that premise.

Activity

  1. theCodeDrift commented on Oct 20, 2026

    @theCodeDrift
    MemberAuthor

    Update: not pinning, and not shrinkwrapping either. The plan is to stay loose in development and tight in release, without changing package.json at build or publish time.

    A published npm-shrinkwrap.json doesn't work for this package. Measured on 2026-10-19 with npm 11.8, installing a repacked 0.13.1 from a local registry, which is needed because npm reads a dependency's shrinkwrap only when the registry flags the package as having one:

    Shrinkwrap contents Result
    Full tree Pins held (tsx 4.21.0, esbuild 0.27.3, get-tsconfig 4.13.6), but every platform's ast-grep (7), Vale (6) and esbuild (26) binaries were installed: 1.0 GB instead of 110 MB
    Platform packages left out tsx pinned, but no ast-grep, Vale or esbuild binary: 2.3 MB, and broken

    npm checks os and cpu while it plans an install, but it unpacks a dependency's shrinkwrap afterwards, during the install, so those entries are never checked. It also treats the shrinkwrap as complete, so anything left out is never resolved. Our native tools ship as per-platform optional packages, which is exactly the case that breaks.

    What replaces it: the nightly and release builds derive an npm lockfile for the CLI from pnpm-lock.yaml, offline. init writes it beside the project launcher as .taskless/bin/cli.lock.json (and cli-nightly.lock.json for the nightly). The launcher installs with npm ci from that file into a git-ignored .taskless/.cache/. Measured with a lockfile built that way: tsx 4.21.0, esbuild 0.27.3, only darwin-arm64 binaries, 109 MB. npm ci also refused when the requested version didn't match the lockfile.

    A bare npx @taskless/cli or pnpm dlx run still resolves ranges; the launcher is the frozen path. This is tracked in the launcher-local-cache OpenSpec change, stacked on #537. This issue closes with that PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions