Skip to content

fix(1512): a trailing space in COMFYUI_PATH stops silently breaking every install-root check - #1523

Merged
artokun merged 4 commits into
mainfrom
fix/1512-trim-comfyui-path
Aug 13, 2026
Merged

fix(1512): a trailing space in COMFYUI_PATH stops silently breaking every install-root check#1523
artokun merged 4 commits into
mainfrom
fix/1512-trim-comfyui-path

Conversation

@artokun

@artokun artokun commented Aug 13, 2026

Copy link
Copy Markdown
Owner

Claims #1512.

COMFYUI_PATH is consumed without trimming, so surrounding whitespace makes every install-root check miss and the connected ComfyUI is reported as undeterminable — 40 minutes after the bad value took effect, at the first write, with a message that echoed the path back but never pointed at the space. It cost a 12.3 GB download, stranded at 11.35 GB and finished by hand.

cmd.exe assigns everything up to the &&, including the space before it, so the launcher line people actually paste bakes one in. The panel pack already strips this; the orchestrator did not — two halves of one product disagreeing.

What the report missed

It names resolveComfyUIPath as "the single ingestion point". There are five non-test readers, and a fix confined to the first is not merely incomplete — it makes one case worse:

Reader What a trailing space does
config.ts resolveComfyUIPath boot + retarget resolution
orchestrator/index.ts feeds the spawn env builders — the bad value reaches every agent this orchestrator starts
panel-tools.ts comfyWorkflowsDirs builds <root> /user/default/workflows, which silently does not exist, so the library reads empty
extra-paths.ts compares raw env against config.comfyuiPath — normalizing only the latter turns an accidental match into a mismatch and reclassifies an explicitly named root as inferred
workspace-env.ts labels the workspace source

The extra-paths row is a regression the partial fix would have introduced.

The repair is a fallback, not a cleanup

Trailing whitespace and quotes are legal POSIX filename characters, so a blanket trim can redirect a caller away from a real directory — a repair doing more damage than the bug. A value that resolves as given is never touched.

The obvious objection is that Windows tolerates trailing spaces, which would make the guard a no-op exactly where the bug lives. Measured on win32 rather than assumed:

existsSync("<root>")                  -> true
existsSync("<root> ")                 -> false     <-- why every check missed
existsSync(join("<root> ","main.py")) -> false
mkdir "WithSpace "                    -> succeeds  <-- so the POSIX case is real here too

Quote stripping removes only a matched pair; a lone trailing quote is left alone.

Also

  • The malformed value is reported at ingestion, both values JSON-quoted so the space is visible, naming the && line that produced it. Warn-once per distinct value. Emitted inside the normalizer so a new ingestion point cannot get normalization while silently forgetting to report.
  • Helper moved to src/utils/install-path-env.ts. config.ts builds module state at import time and many suites mock it wholesale; importing a pure string helper from it turned 46 tests red for missing mock exports — harness defects, not product bugs.

Verification

  • 12 tests, 11/11 mutations killed — including deleting the normalizer at each of the five sites independently.
  • The source gate requires the raw read to be an argument, not merely to have a normalizer nearby. Codex's counterexample (normalizer called, result discarded, raw forwarded) was built and confirmed rejected.
  • One redundant guard deleted on its own evidence: it killed no mutation because the only caller already gates on changed.
  • Suite 491 files / 9239 tests; all six repo gates clean.

Out of scope, from the report: download_model action:"status" cannot see partials whose job records were pruned, so orphaned .partial files are unreclaimable. Real, but a different feature.

Copilot AI balanced review requested due to automatic review settings August 13, 2026 11:31

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review any files in this pull request.


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

artokun and others added 3 commits August 13, 2026 04:40
… malformed

One trailing space made every install-root check miss and the connected ComfyUI
was reported as undeterminable — 40 minutes after the bad value took effect, at
the first write, with a message that echoed the path back but never pointed at
the space. It cost a 12.3 GB download, stranded at 11.35 GB and finished by hand.

cmd.exe assigns everything up to the `&&`, INCLUDING the space before it, so the
launcher line people actually paste bakes one in:

    cmd /k "set COMFYUI_PATH=E:\...\ComfyUI && comfyui-mcp connect ..."

The panel pack already stripped this; the orchestrator did not. Two halves of one
product disagreeing is the defect.

The report names resolveComfyUIPath as "the single ingestion point". It is not.
orchestrator/index.ts reads process.env.COMFYUI_PATH DIRECTLY, and what that
produces is handed to the spawn env builders — so a fix confined to config.ts
would leave the bad value reaching every agent the orchestrator starts while
looking fixed locally. Both now share one normalizer so they cannot drift.

Narrower than the proposed patch in two places, on purpose:

  - quote stripping removes only a MATCHED leading+trailing pair. The proposed
    `replace(/^["']|["']$/g, "")` also strips a LONE trailing quote — illegal in
    a Windows filename but legal on POSIX, so it could corrupt a real path to fix
    a typo. The repair must not be able to do more damage than the bug.
  - a whitespace-only value normalizes to UNSET, so detection still runs instead
    of adopting "   " as a path.

Also builds the reporter's follow-up: the malformed value is REPORTED at
ingestion, with both values JSON-quoted so the offending space is visible, naming
the launcher line that produced it. Warn-once per distinct value — retarget
re-resolves on every switch.

11 tests, 7/7 mutations killed. The orchestrator call site sits in a startup
function no unit test can reach, so it is pinned by a source assertion: no raw
read of that variable may survive un-normalized.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… 5 readers

P1 (destructive on valid POSIX paths). Trailing whitespace and quotes are LEGAL
POSIX filename characters, so a blanket trim can redirect a caller away from a
real directory — a repair doing more damage than the bug. The normalization is
now a strict FALLBACK: a value that RESOLVES as given is never touched.

Measured on win32 rather than assumed, because the obvious worry is that Windows
tolerates trailing spaces and would make the guard a no-op there:

    existsSync("<root>")            -> true
    existsSync("<root> ")           -> false
    existsSync(join("<root> ",...)) -> false
    mkdir "WithSpace "              -> succeeds (so the POSIX case is real here too)

The false result is exactly why every install-root check missed, so the guard
keeps the fix while making it unable to touch a path that works.

P1 (raw consumers beyond the asserted site). Found independently while auditing
the same question; there are FIVE readers, not two. The extra-paths one is the
sharp edge: it compares the raw env against config.comfyuiPath, so normalizing
only the latter turns an accidental match into a MISMATCH and silently
reclassifies an explicitly named root as "inferred". Fixing one end alone would
have introduced that.

P2 (source assertion was a rubber stamp). It required a normalizer "within 3
lines", which passes when the result is discarded and the raw value forwarded
anyway. It now requires the read to BE an argument, so the raw value has no name
to be forwarded under — verified by building codex's counterexample and watching
the gate reject it.

The helper moved to src/utils/install-path-env.ts. config.ts builds its
module-level config at IMPORT time, so many suites mock it wholesale; putting a
pure string helper behind it turned 46 tests red for missing mock exports, none
of them a product defect. A leaf module has no such gravity.

One redundancy removed on its own evidence: a second `raw === normalized` guard
inside the warn helper killed no mutation, because the only caller already gates
on `changed`. Undetectable by construction is not defensive.

12 tests, 11/11 mutations killed. Suite 491 files / 9239 tests.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…bing needlessly

P2: the source gate was LINE-level, so a line whose first occurrence was an
argument exempted the rest of it:

    const p = normalizeInstallPathEnv(process.env.COMFYUI_PATH).path; f(process.env.COMFYUI_PATH);

It now counts occurrences against guarded occurrences, so the tally has to
account for all of them. Verified by building that exact line and watching the
gate reject it — reported as "(1/2 consumed)".

P2: existsSync ran for EVERY non-empty value, including well-formed ones that
the repair cannot touch. Five readers call this, some hot, and it replaced a
plain env read — a stat per call is new synchronous I/O everywhere, and on a
UNC/network root it can block. The order is inverted: compute the repair first
and return immediately when there is nothing to change, so the disk is consulted
only when its answer decides something. Pinned by counting probes rather than
asserting the property: zero for clean/empty/undefined, exactly one for a value
that would change.

The TOCTOU codex notes is inherent to any existence-based fallback and benign
both ways — recorded in the source rather than left for the next reader to
rediscover.

13 tests, 11/11 mutations killed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@artokun
artokun marked this pull request as ready for review August 13, 2026 12:07
@artokun
artokun merged commit ca10f23 into main Aug 13, 2026
5 checks passed
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