Sessions started for implementation work should carry IW_METALS=1 #799

Open
opened 2026-10-15 12:50:04 +00:00 by claude · 1 comment
Member

The Metals MCP launcher on this machine now starts Metals only when IW_METALS=1 is in the environment that starts Claude Code. The default is off. iw-cli is the component that knows when a session is implementation work, so it is the right place to set it.

WHY THE GATE EXISTS

Measured over the 14 days to 2026-10-01, across both Claude config trees: of 382 sessions where a Metals tool became available, 86 called it and 296 did not. Each of those 296 paid for a Metals JVM of about 1 GB plus a build-server child beside it.

On 2026-10-15, 18 Metals JVMs and their build-server children held 40.7 GB across 44 processes, on a 128 GB box with 3.3 GB of RAM free and 74 GB in swap. Nothing is shared: every session gets its own Metals JVM and its own BSP/Bloop/Mill daemon, so three sessions in one repository pay three times over for the same build. Subagents inherit the parent session's connection rather than opening their own.

The launcher's only test used to be whether the directory holds build.sbt, build.mill or build.sc. A build file says what the PROJECT is. It never says whether THIS session will ask Metals anything, and that is the question that decides the cost.

Of the 86 sessions that did call Metals, 84 used a build tool (compile-module, compile-file, compile-full, test, format-file, list-modules) and only 26 touched a search tool. So the sessions worth paying for are the ones that compile.

WHAT IS ASKED

A session that iw-cli launches for implementation work should carry IW_METALS=1 in its environment. The obvious candidates:

  • iw start <ISSUE-ID> and iw open <key>, where the SessionAction hook launches a workflow session in a worktree
  • the phase sessions that batch-implement spawns, which are exactly the compiling ones

A session opened for triage, analysis, review reading or a docs edit does not need it and should not carry it.

There is precedent for iw-cli composing a session environment deliberately: loop sessions already start with an allow-listed environment, and batch-implement already drops IW_CORE_JAR, JAVA_OPTS, JDK_JAVA_OPTIONS and XDG_CACHE_HOME from its children on purpose. This is the same kind of decision in the other direction.

A design note rather than a requirement: a per-repository switch in .iw/config.conf would let a Scala repository opt in while leaving non-Scala repositories untouched, which keeps the flag from becoming unconditional. Whether that is worth the configuration surface is your call.

WHAT HAPPENS UNTIL THEN

Metals starts nowhere unless the variable is exported by hand (IW_METALS=1 claude, or an export in a repository's .envrc). That is the intended default and it is deliberate, but it means the 84 sessions a fortnight that compile through Metals currently lose it unless the operator remembers. That is the gap this issue closes.

The launcher lives outside iw-cli, in a home-manager service, so no iw-cli change is required for the gate itself - only for setting the variable on the sessions iw-cli starts.


Type: feature
Filed from: /home/mph/ops/kanon-DEVDOCS-293 on branch drop-zg

The Metals MCP launcher on this machine now starts Metals only when IW_METALS=1 is in the environment that starts Claude Code. The default is off. iw-cli is the component that knows when a session is implementation work, so it is the right place to set it. WHY THE GATE EXISTS Measured over the 14 days to 2026-10-01, across both Claude config trees: of 382 sessions where a Metals tool became available, 86 called it and 296 did not. Each of those 296 paid for a Metals JVM of about 1 GB plus a build-server child beside it. On 2026-10-15, 18 Metals JVMs and their build-server children held 40.7 GB across 44 processes, on a 128 GB box with 3.3 GB of RAM free and 74 GB in swap. Nothing is shared: every session gets its own Metals JVM and its own BSP/Bloop/Mill daemon, so three sessions in one repository pay three times over for the same build. Subagents inherit the parent session's connection rather than opening their own. The launcher's only test used to be whether the directory holds build.sbt, build.mill or build.sc. A build file says what the PROJECT is. It never says whether THIS session will ask Metals anything, and that is the question that decides the cost. Of the 86 sessions that did call Metals, 84 used a build tool (compile-module, compile-file, compile-full, test, format-file, list-modules) and only 26 touched a search tool. So the sessions worth paying for are the ones that compile. WHAT IS ASKED A session that iw-cli launches for implementation work should carry IW_METALS=1 in its environment. The obvious candidates: - `iw start <ISSUE-ID>` and `iw open <key>`, where the SessionAction hook launches a workflow session in a worktree - the phase sessions that `batch-implement` spawns, which are exactly the compiling ones A session opened for triage, analysis, review reading or a docs edit does not need it and should not carry it. There is precedent for iw-cli composing a session environment deliberately: loop sessions already start with an allow-listed environment, and batch-implement already drops IW_CORE_JAR, JAVA_OPTS, JDK_JAVA_OPTIONS and XDG_CACHE_HOME from its children on purpose. This is the same kind of decision in the other direction. A design note rather than a requirement: a per-repository switch in .iw/config.conf would let a Scala repository opt in while leaving non-Scala repositories untouched, which keeps the flag from becoming unconditional. Whether that is worth the configuration surface is your call. WHAT HAPPENS UNTIL THEN Metals starts nowhere unless the variable is exported by hand (`IW_METALS=1 claude`, or an export in a repository's .envrc). That is the intended default and it is deliberate, but it means the 84 sessions a fortnight that compile through Metals currently lose it unless the operator remembers. That is the gap this issue closes. The launcher lives outside iw-cli, in a home-manager service, so no iw-cli change is required for the gate itself - only for setting the variable on the sessions iw-cli starts. --- **Type:** feature **Filed from:** `/home/mph/ops/kanon-DEVDOCS-293` on branch `drop-zg`
Author
Member

Intake: needs-input

1. Do we move this issue to the dev-docs repository, where the code that starts these sessions is?

The issue says that iw-cli starts the implementation sessions. It does not. In iw-cli, iw start and iw open only run whichever session command a plugin hook gives them, and rg IW_METALS finds nothing in iw-cli. The claude command line comes from the kanon plugin hook:

s"claude --dangerously-skip-permissions $shellSafe"

(claude-session.hook-start.scala#L16). The batch-implement child environment, including the list of variables it removes, is also in the plugin (BatchImplementation.scala#L142-L160).

  • a) Move the issue to dev-docs. The plugin hook and ClaudeEnvironment set IW_METALS=1, and iw-cli does not change. Close IW-799 here.
  • b) Keep IW-799 for an iw-cli part only. iw-cli adds a per-repository key to .iw/config.conf (for example metals = true) that the plugin reads. A second issue in dev-docs sets the variable. This adds configuration surface across two repositories.
  • c) Move the launch decision into iw-cli. iw-cli adds an environment field for session commands that hooks can fill. This is larger and changes the hook contract.

Recommended: a) — this changes the least: the two edits go in the code that starts the sessions, and no new configuration or contract is needed.

Answer in a comment on this issue, per number, for example "1: a", or in your own words; intake reads it on its next iteration.

Intake: needs-input ### 1. Do we move this issue to the dev-docs repository, where the code that starts these sessions is? The issue says that iw-cli starts the implementation sessions. It does not. In iw-cli, `iw start` and `iw open` only run whichever session command a plugin hook gives them, and `rg IW_METALS` finds nothing in iw-cli. The `claude` command line comes from the kanon plugin hook: > `s"claude --dangerously-skip-permissions $shellSafe"` ([claude-session.hook-start.scala#L16](https://code.iterative.works/iterative-works/dev-docs/src/branch/main/iw-plugin/hooks/claude-session.hook-start.scala#L16)). The `batch-implement` child environment, including the list of variables it removes, is also in the plugin ([BatchImplementation.scala#L142-L160](https://code.iterative.works/iterative-works/dev-docs/src/branch/main/iw-plugin/lib/batch/BatchImplementation.scala#L142-L160)). - **a) Move the issue to dev-docs.** The plugin hook and `ClaudeEnvironment` set `IW_METALS=1`, and iw-cli does not change. Close IW-799 here. - **b) Keep IW-799 for an iw-cli part only.** iw-cli adds a per-repository key to `.iw/config.conf` (for example `metals = true`) that the plugin reads. A second issue in dev-docs sets the variable. This adds configuration surface across two repositories. - **c) Move the launch decision into iw-cli.** iw-cli adds an environment field for session commands that hooks can fill. This is larger and changes the hook contract. **Recommended: a)** — this changes the least: the two edits go in the code that starts the sessions, and no new configuration or contract is needed. Answer in a comment on this issue, per number, for example "1: a", or in your own words; intake reads it on its next iteration.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
iterative-works/iw-cli#799
No description provided.