Taskless v0.13.7: Oxlint Rules and Rollback-Ready Writes

Jakob Heuser

0.13.7 is a patch release with 19 changesets. Most of them close gaps where Taskless reported success for work it didn't do. check now fails when a rejected token, an unsupported CLI, or an edited runtime rule kept your rules from running, so a pipeline that passed on 0.13.6 can go red on this one. Your agent also turns on Oxlint rules the same way it does for Ruff, detect learns C#, and writing a rule now either finishes or leaves .taskless/rules/ as it was.

npx @taskless/cli

check fails when enforcement didn't run

A green check should mean your rules ran. Three cases broke that, and each one now exits 1 with a failure that says what happened:

What went wrong

Before

Now

The rule service rejected the token (a revoked or mistyped TASKLESS_TOKEN)

Static rules ran unverified, runtime rules skipped, exit 0

Same scan, exit 1, failure says authentication was rejected

The service no longer supports this CLI version

Unverified scan, exit 0 with a notice

Exit 1, failure names the upgrade

A runtime rule's signed files were edited

Rule answered unsafe, skipped, listed under notices

Listed under failures, exit 1

An edited runtime rule now fails the run the same way an edited ast-grep or Vale rule already did. taskless rule restore <id> puts it back.

A service you can't reach is still non-fatal. A Taskless outage won't turn your CI red.

check and verify also refuse an engine binary of the wrong version. When the pinned platform package is missing, the CLI falls back to an sg or vale from node_modules/.bin or PATH. It now reads that binary's --version first, and if it doesn't match the engine this CLI bundles, it stops with ENGINE_BINARY_MISMATCH and names the binary's path and version. Pass --allow-version-mismatches to run it anyway.

Deeper Oxlint support

detect has found Oxlint since 0.11. Now your agent can work with it. When you ask for a rule in a repository that runs Oxlint, the agent follows the same order it does for Ruff, ESLint, and Biome, and reaches for a rule Oxlint already ships before it writes anything new. taskless agent create-legacy-rule --tool=oxlint serves the guide.

It starts with Oxlint's catch-all rules, since searching for the request's subject won't surface them. no-restricted-imports bans modules or named imports with a message, and no-restricted-globals bans global names. Both do nothing until they're given a list:

"rules": {
"no-restricted-imports": ["error", {
"paths": [{ "name": "lodash", "message": "Use the helpers in src/util." }]
}]
}

Oxlint has no no-restricted-syntax, so a request that needs an AST selector becomes a Taskless rule.

The guide covers the traps that leave an Oxlint rule silent:

  • The agent confirms each rule with oxlint --rules --format default. Without --format default, Oxlint prints nothing when it detects an agent or GitHub Actions, and still exits 0.
  • Setting plugins replaces Oxlint's default set, so a plugin's rule only fires when the config keeps that plugin on.
  • Each file uses its nearest config, and a nested config replaces its parent unless it says extends. The agent edits the config nearest the files in question.
  • A repository running ESLint beside Oxlint through eslint-plugin-oxlint has the rules Oxlint enables turned off in ESLint, so a rule that exists in both gets enabled in Oxlint.

The agent doesn't write Oxlint plugins, since Oxlint's JavaScript plugin API is still in alpha. When nothing Oxlint ships fits, it writes a Taskless rule.

Every command says when the CLI needs an upgrade

When the service drops support for the running CLI version, every command that talks to it now says so and tells you how to upgrade. It used to report a network failure, which reads like something to retry. Under --json the code is CLI_UPGRADE_REQUIRED, where it used to be NETWORK_ERROR.

info tells you whether your token works

taskless info used to print "not logged in" for an expired token, a rejected token, and an unreachable service alike, while info --json reported loggedIn: true for all three. It now checks the token with the service and reports which one happened, with what to do about it. The JSON carries a new authState field:

authState

Meaning

authenticated

The service accepted the token

rejected

The service refused it

unreachable

The service couldn't be reached

upgrade_required

The service no longer supports this CLI version

none

No token

anonymous

--anonymous skipped the check

loggedIn is true only for authenticated.

agent ok, check, and info also report a stale or missing Taskless skill in a project with no agent harness directory (no .claude/, .codex/, .gemini/, and so on). The skill installs to .agents/ there, and until now nothing looked at it, however old it got.

detect learns C#

taskless detect recognizes C# repositories from a .csproj, along with three analyzers:

  • Roslynator, from its NuGet packages in an MSBuild file, packages.config, or dotnet-tools.json, its keys in .editorconfig or .globalconfig, a .roslynatorconfig, or a CI or hook command that runs it.
  • StyleCop and SonarAnalyzer, each reported as its own linter, from its NuGet package, its config file, or its diagnostic ids.

It also reads two more places a repository keeps its lint setup. ESLint rule directories (eslint-rules, eslint-local-rules, tools/eslint-rules) are found beside every ESLint config, so a root run on a repository whose ESLint lives in dashboard/ now counts the rules in dashboard/eslint-rules. And scripts in a tracked .githooks/ directory show up under hooks as githooks, with any linter they run counted as detected. core.hooksPath isn't read, since a fresh clone doesn't carry it.

agent route now names every detected tool that can host a rule, so a linter it found no longer reads as missing.

Rule writes finish or leave nothing behind

rule create, rule improve, rule restore, and rule rollback used to be able to leave a partial result on disk. create and improve wrote each rule as it came, so a request whose third rule was refused left the first two in place. A full disk or an unwritable file could leave a rule half-written.

Every rule a request produces is now checked first: its engine, its paths, and whether its file set is complete. Then each one is staged under .taskless/ and moved into place. If anything fails part-way, rules already replaced are moved back, and .taskless/rules/ ends up exactly as it started. The JSON codes are specific now as well:

  • RULE_GENERATION_FAILED or RULE_UNSUPPORTED when create or improve is refused or can't write
  • RULE_RESTORE_FAILED when restore or rollback can't write, and RULE_RESTORE_MISMATCH when the served rule can't be written

All of these used to come back as INTERNAL_ERROR. A restore also replaces the rule directory as a whole now, so files the served rule doesn't include are removed.

create and improve also stop reporting success for a request that delivered nothing. A request that ends in pr, merged, or closed, in a status the CLI doesn't know, or with no status at all, fails with RULE_GENERATION_FAILED and names the state. It used to print the success envelope with rules: [] and exit 0. While waiting, an unfamiliar status keeps the poll going, the service's pushing step gets its own progress line, and the wait stops after 30 minutes with a NETWORK_ERROR that names the last status and the --resume command to keep waiting.

A local build of Taskless can adopt a nightly

A project that once installed a local development build of the CLI refused every nightly and release afterwards. The CLI decided which channel a recorded version belonged to by looking for a dash in it, so a local build's version read as a nightly that was never published. The CLI now knows whether it's a release, a nightly, or a local build from how it was built, so the three stop refusing each other.

The fix moves the .taskless/ scaffold to version 13. The version each launcher runs now comes from install.launchers alone, and the migration moves a version recorded only in the older install.cliVersion field into it.

The next init your agent runs applies the migration. An older CLI refuses a migrated project as newer than it understands, so update any CLI pinned in CI or a hook along with the migrated files.

onboard and report no longer migrate .taskless/ on their own. On a project whose scaffold is behind, they refuse with SCAFFOLD_MIGRATION_REQUIRED, the way check, validate, and verify already did. rule create, rule improve, rule restore, and rule rollback refuse the same way, before they contact the service. Each of them used to move and delete tracked files under .taskless/ without saying so.

auth logout means logged out

auth logout now cancels a login that's still waiting for approval, interactive or --json. Approving that login afterwards used to save a token and log the repository back in. The waiting login now fails with AUTH_REQUIRED and saves nothing.

An expired interactive code reports AUTH_EXPIRED, matching --json, and tells you to run auth login again for a new code. A failed interactive login prints its error once instead of twice. auth login --json also polls at least once per step when the service asks for an interval longer than the step's 45-second wait. Before, every step came back pending until the code expired.

Smaller fixes

  • verify --json reports a status on each rule: passed, failed, not-run, or refused. Branch on status, since reading ok alone could count a rule that never ran as passing. ok, ran, and refused are still there and now come from status.
  • init puts back a Taskless allow rule that a hand edit, a revert, or a merge removed from a harness permission file like .claude/settings.json or .cursor/cli.json, and recreates the file if it was deleted.
  • update --rules from an older nightly of the same release refuses and says to upgrade, where it used to rewind the recorded engine versions. It also refuses when the rules were recorded against newer engines than this CLI bundles, and info, init, and update list those under reconcile.enginesAhead in --json.
  • update --json on an unreadable .taskless/taskless.json exits 1 with SCAFFOLD_MANIFEST_UNREADABLE.
  • agent ok stops telling your agent to wait for a newer CLI when only the skill needs reinstalling.
  • report --from keeps a report's delete URL when two publishes land in the same second. A taken record name moves to a _2, _3 suffix, which --delete accepts. If the record still can't be written, the command exits with REPORT_RECORD_FAILED and prints both URLs.
  • The @taskless/cli/prompts recipes cite other recipes by topic when mechanics: false, getRawInstructions returns the template's defaults, and create-runtime-rule covers four ast-grep capture gotchas.
  • The bundled ast-grep moves to 0.51.1.

The full changelog is on taskless/cli#618.

Getting it

npx @taskless/cli