# prod-code 0.3.52: read-only tools that say so, and failures that name the way on

> Source: <https://prod.codes/blog/prod-code-0-3-52-failures-that-name-the-way-on/>
> Published: 2026-11-06 00:00:00+00:00

# prod-code 0.3.52: read-only tools that say so, and failures that name the way on

More tools declare in the MCP catalog that they never write, shell and assembly files get outlines without a language server, and silent failures now explain themselves. Built and signed by a hosted workflow using Mitts; generated Rust commands still run cargo.

## On this page · 8 sections

Most of [prod-code v0.3.52](https://github.com/alex09x/prod-code/releases/tag/v0.3.52)
(commit `91ec5def`, published 2026-11-05) fixes cases where a call failed without giving a
reason, or reported success when it had not done the work. This post follows the `v0.3.52`
section of the
[changelog at the tag](https://github.com/alex09x/prod-code/blob/v0.3.52/CHANGELOG.md); each
item below carries its issue number. The Zenodo citation for prod-code is still the
[v0.3.49 record](https://doi.org/10.5281/zenodo.23905176). There is no archived v0.3.52 version
yet.

## Read-only tools say so

An MCP host whose approval policy is `never` refuses any tool it cannot tell is read-only
(`MCP tool call requires approval, but approval policy is never`), and the agent falls back to
the command line. Until this release only the navigation tools carried `readOnlyHint`. Eleven
more now carry it: `code_impact`, `code_diagnostics`, `code_validate_edit`,
`code_validate_edits`, `code_assists`, `code_dead_code`, `code_dependencies`,
`code_find_duplicates`, `code_structural_search`, `code_propose_expression` and `code_status`.
Tools that write, run a command, or are read-only only for some arguments stay unmarked. A test
checks the marked list against the server’s own definition of a read-only tool (#1887, #1890,
#1900, #1905).

## Files without a language server

No language server answers a shell symbol. `code_definition NAME` now finds a function defined
in a `.sh`, `.bash` or `.zsh` script, with its body. If two scripts define the same name, both
are listed as candidates instead of one being guessed (#1940). A shell script’s outline is now
read from the file itself. It covers all three function forms, and it skips comments and
here-document bodies. `.s`, `.S` and `.asm` files are outlined the same way, from `.globl`
symbols, labels and `.macro` definitions (#1851, #1935). An outline that is read from the file
no longer asks the cluster for a node that serves its language. A directory outline that mixes
languages outlines what it can reach, and counts the files of an engine that did not start as
skipped (#1898).

## Failures that explain themselves

- **A silent timeout.** A command runs on the node with standard input closed and no terminal.
One that waits for a login, a browser or a prompt therefore waits until its deadline and
prints nothing. When a command times out with no output, the report now says why and suggests
a non-interactive option or another place to authenticate (#1892).
- **Dropped standard input.**`prod-code exec -- python3 - <<'PY' … PY` used to run an empty
program on the node and report success, because the command line does not carry standard
input to the node. When input is waiting,`exec` now stops before anything runs. It suggests
putting the program in a file in the checkout or passing it as an argument. Standard input is
still not forwarded (#1944).
- **An unreachable node.** Connecting to a node that does not answer used to take three
operating-system connect timeouts in a row and end in a bare “timed out”. Each attempt now
waits at most 8 seconds. The error names the node and points to`prod-code status` ,`--remote` or`--pin-node` (#1938).
- **A Swift rename that missed files.** sourcekit-lsp renames only what its index knows, and a
call in another file is not indexed until the target is built.`code_rename` used to report
success anyway. It now lists the Swift files that still contain the old name, with the advice
to build and rename again (#1953). This reports the gap; it does not close it.

## Answers about the right checkout

`code_impact` now accepts the same `workspace` selector as `code_exec` and the read tools. From
a PR worktree, the report covers that worktree’s diff. Before, it described the configured
checkout: 290 changed files for a two-file change (#1939). It also stops naming
`swift test` for Swift tests in an Xcode project without a `Package.swift`, and reports that
group as unsupported (#1951). `code_source` now reads a dependency’s source when `workspace` is
set; paths with `..` are still refused (#1955).

## Smaller changes

- Moving a private Rust item into a sibling module now makes it `pub(crate)` when another module
still names it, and the move report says so (#1933).
- Files that an `exec` creates on the node’s copy, such as a scratch script or a generated
framework, are no longer deleted by the next connection’s sync (#1895, #1888).
- A tracked symlink that the node will not create is now a warning. It used to be an error that stopped every command in the workspace (#1889).

Nothing was removed. The release makes two behaviours stricter or quieter on purpose: `exec`
refuses input it would drop, and the symlink error is now a warning.

## How the release itself was built

This is the first prod-code release whose binaries were built, signed and published by a GitHub
Actions workflow on standard hosted runners (#1852). The workflow builds with
[Mitts](https://prod.codes/projects/mitts/), the author’s Rust build tool, and CI runs formatting, Clippy and the
tests through it. The release run for the tag took about 30 minutes. The Linux ARM64 build took
13 minutes, Linux x86-64 16 and macOS ARM64 28, then signing and collection ran. The release
page lists six binaries, the client and the server for Linux x86-64, Linux ARM64 and macOS ARM64,
plus `SHA256SUMS`. The macOS binaries are Developer ID signed and Apple notarized. I checked each
line of `SHA256SUMS` against the digest GitHub records for that asset, and all six match.

CI previously ran each shard’s test targets one after another without a concurrency flag. Now
each shard passes `--concurrency` (#1930). The last full-workspace run before the tag is recorded
in [PR #1956](https://github.com/alex09x/prod-code/pull/1956), after which only the version bump
followed. On one 32-core Linux x86-64 node, `mitts test --workspace --no-fail-fast -c` ran 5569
tests in 663 seconds. All 5569 passed and 23 were skipped.<sup>[1](#user-content-fn-1)</sup> Two tests that read the machine’s Cargo crate cache were rewritten so that they pass
with an empty `CARGO_HOME` (#1930). A stack-depth failure in the Go signature tests and a race
in the hot-reload drain were also fixed (#1941, #1814).

## What did not change

Mitts builds and tests prod-code itself. The commands prod-code generates for a user’s Rust
project are different: `code_test`, `code_check` and `code_lint` still run `cargo`. In
`verify/plan/basic.rs` at the tag, a Rust test is still `cargo test --workspace`. Switching
them was tried and reverted before the release. As
[PR #1952](https://github.com/alex09x/prod-code/pull/1952) records, Mitts rejects `--quiet` and
spells `check` differently, and 147 tests failed. A setting to choose another runner per project
is not part of this release.

## Footnotes

1. 
The changelog at the tag gives 5553 tests in 660 seconds for #1930. That entry was written before #1954 and #1956 added tests. The tag cannot change, so its changelog keeps the older figure; the entry on prod-code’s main branch is being corrected to match PR #1956. [↩](#user-content-fnref-1)

## Cite this article

```
Alexander Panasenko (2026-11-06). prod-code 0.3.52: read-only tools that say so, and failures that name the way on. https://prod.codes/blog/prod-code-0-3-52-failures-that-name-the-way-on/
```


