fix(cascade-tools): explicit help topics + envelope-only stdout + pin strict mode#1285
Conversation
When `pjson.oclif.topics` is unset, oclif borrows each topic's description
from its first command (`node_modules/@oclif/core/lib/config/config.js` —
`this._topics.set(name, { description: c.summary || c.description, name })`).
That made bare `cascade-tools --help` show:
pm Add a checklist with items to a work item. ...
scm Create a GitHub pull request. Handles the full workflow ...
alerting Retrieve full details for an alerting event ...
session Call this gadget when you have completed all tasks ...
Agents reading bare --help to map the surface got a misleading frame (saw
≥ 2× in the 2026-05-09 prod corpus). Set explicit topic summaries so each
TOPICS line is one truthful sentence per topic.
Also covers the two direct-provider topics (`github`, `trello`) that mirror
the canonical `scm` / `pm` surfaces; their summaries point operators back to
the provider-agnostic topic.
Regression net at tests/unit/cli/cascade-tools-help.test.ts pins both the
borrowed-description-must-not-appear and canonical-summary-must-appear
invariants for all six topics.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Locks oclif's documented default (`strict = true`) explicitly. Without it, unknown flags would slip past parse validation and reach the gadget body as positional args — silently bypassing the spec-014 `unknown-flag` envelope even though every cascade-tools command claims to honor it. Adds a regression test on the factory-generated command surface that asserts unknown flags fire the structured `unknown-flag` envelope (passes today; guards against any future drift if oclif loosens the default). Note: the original 2026-05-09 analysis read run 27be3592 as a strict-mode gap (`session finish --agent-type review --review-submitted --comment ...` returning success despite `finishDef` declaring only `comment`). Closer inspection of `src/cli/session/finish.ts` shows it's a hand-written oclif command — `--agent-type`, `--pr-created`, and `--review-submitted` are legitimate CLI extensions, listed in `--help`. Not a real bug. The pin in this commit is preventive, not a fix. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…elope-only
Prod 2026-05-09: 75/120 cascade-tools calls (62%) emitted DEBUG/INFO log
lines + ANSI escapes on stdout BEFORE the JSON envelope, polluting the
agent's tool-result channel.
Root cause: the worker process at `src/backends/llmist/index.ts:83-84`
sets `LLMIST_LOG_FILE=<engineLogPath>` AND `LLMIST_LOG_TEE='true'` so its
own logger tees to both file and stdout. Both env vars are in the
subprocess allowlist (`src/utils/cascadeEnv.ts:14-15`) and pass through to
the bash subprocess that runs cascade-tools. The cascade-tools logger
then ALSO tees, polluting the agent's view.
Fix: strip `LLMIST_LOG_TEE` at the very top of `bin/cascade-tools.js`
(before the singleton logger reads env). With `LLMIST_LOG_FILE` still set,
all logs (including the load-bearing `[image-pipeline] work-item-fetch
summary` per spec 016 / `src/integrations/README.md`) land in the engine
log file the worker collects — operator observability via `cascade runs
logs <runId>` is preserved. For standalone runs (no LLMIST_LOG_FILE
inherited), redirect to /dev/null so dev runs stay envelope-only too;
developers can override with `LLMIST_LOG_FILE=/tmp/x.log cascade-tools ...`.
No source-tree code changes — `src/utils/logging.ts`, the cascade logger
usage everywhere, and the `[image-pipeline]` log line all unchanged. The
fix is one entrypoint env tweak.
Regression net at tests/unit/cli/cascade-tools-stdout-cleanliness.test.ts
spawns cascade-tools as a subprocess under three env shapes (worker /
standalone / dev-override) and asserts stdout matches `^{"success":` with
no ANSI escapes (ESC byte 0x1b absent), no `[cascade]` substring, and no
tab-separated log-level prefixes. Also asserts the engine log file DOES
receive the cascade logger output — the operator-observability invariant.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
nhopeatall
left a comment
There was a problem hiding this comment.
Summary
CI is failing because the help-topic regression test is pinned to legacy topics that are not present in a clean build.
Code Issues
Blocking
- bin/cascade-tools.js:68 — The new
github/trellotopic entries do not have any matching commands in the current source tree, so a fresh build has no child topics for oclif to render. I verified bothHEADandorigin/devhave nosrc/cli/github/*orsrc/cli/trello/*; oclifHelp._topicsonly shows configured topics with child topics/commands. This means the new test expectations forgithubandtrelloonly pass with staledist/cli/github/dist/cli/trelloartifacts, and current CI already reportslint-and-testfailing in the test step. Drop these dead topics from the topic block/test, or restore real legacy commands if they are meant to exist.
🕵️ codex · gpt-5.5 · run details
| }, | ||
| alerting: { description: 'Inspect Sentry alerting issues and events.' }, | ||
| session: { description: 'End the agent session. Exclusive terminal call.' }, | ||
| github: { |
There was a problem hiding this comment.
There are no src/cli/github/* or src/cli/trello/* commands in this branch or in origin/dev; bin/cascade-tools.js only loads commands from ./dist/cli. In a clean build these configured topics have no child commands, and oclif filters such topics out of root help, so the new test expectations for github/trello depend on stale dist artifacts. Please remove these dead topic entries/tests or restore actual legacy commands.
There was a problem hiding this comment.
Fixed. Removed both and from the topics block in bin/cascade-tools.js — confirmed there are no src/cli/github/* or src/cli/trello/* commands in the source tree, and oclif silently filters out topics with no child commands on a clean build. Removed the corresponding test assertions (expect(stdout).toMatch(/github.../) and expect(stdout).toMatch(/trello.../)) from cascade-tools-help.test.ts as well. All unit tests pass.
Remove `github` and `trello` from the explicit topics block in bin/cascade-tools.js and the matching test assertions. There are no src/cli/github/* or src/cli/trello/* commands in the source tree, so oclif filters these topics out of root help on a clean build. The test expectations only passed against stale dist artifacts, causing CI failures. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
nhopeatall
left a comment
There was a problem hiding this comment.
LGTM - the entrypoint now strips inherited tee logging before bootstrap, preserves file logging for worker observability, pins strict parsing on the shared command base, and the CI-backed regression tests cover the help and stdout behavior.
🕵️ codex · gpt-5.5 · run details
Summary
Three independent fixes for the remaining issues from the 2026-05-09 prod-run analysis. PR #1281 (merged) closed the dominant boolean-flag pair; this closes the rest.
#1 —
cascade-tools --helptopic summaries are explicitWhen
pjson.oclif.topicsis unset, oclif borrows each topic's description from its first command (seenode_modules/@oclif/core/lib/config/config.js). Barecascade-tools --helpshowed:— each line is a specific gadget's description leaking into the topic line. Agents reading bare
--helpto map the surface got a misleading frame (≥ 2× in the 2026-05-09 corpus).Fix: explicit
pjson.oclif.topics = {...}block inbin/cascade-tools.jscovering all six discovered topics (pm,scm,alerting,session, plus the legacy direct-providergithub/trellowhose summaries point operators back to the canonical topic).#2 —
cascade-toolsstdout is envelope-only (drop inherited LLMIST_LOG_TEE)Prod 2026-05-09: 75/120 cascade-tools calls (62%) emitted DEBUG/INFO log lines + ANSI escapes on stdout BEFORE the JSON envelope, polluting the agent's tool-result channel.
Root cause: the worker process at
src/backends/llmist/index.ts:83-84setsLLMIST_LOG_FILE=<engineLogPath>ANDLLMIST_LOG_TEE='true'so its OWN logger tees to both the engine log file AND stdout. Both env vars are in the subprocess allowlist (src/utils/cascadeEnv.ts:14-15) and pass through to the bash subprocess that runs cascade-tools — making cascade-tools' logger ALSO tee, polluting the agent's view.Fix: strip
LLMIST_LOG_TEEat the very top ofbin/cascade-tools.js(before the singleton logger reads env). WithLLMIST_LOG_FILEstill set, all logs (including the load-bearing[image-pipeline] work-item-fetch summaryper spec 016 /src/integrations/README.md"Diagnostic log line" contract) land in the engine log the worker collects — operator observability viacascade runs logs <runId>is preserved. For standalone runs, redirect to/dev/nullso dev runs stay envelope-only too; developers can override withLLMIST_LOG_FILE=/tmp/x.log cascade-tools ….No source-tree code changes —
src/utils/logging.ts, the cascade logger usage everywhere, and the[image-pipeline]log line all unchanged. The fix is one entrypoint env tweak.#3 — Pin oclif strict mode on
CredentialScopedCommandLocks oclif's documented default (
strict = true) explicitly on the cascade-tools command base. Without it, unknown flags would slip past parse validation and reach the gadget body as positional args — silently bypassing the spec-014unknown-flagenvelope. Preventive; passes today, guards against future drift.Note: the original 2026-05-09 analysis flagged run
27be3592(session finish --agent-type review --review-submitted --comment "..."returning exit=0) as a strict-mode gap. Closer inspection ofsrc/cli/session/finish.tsshows it's a hand-written oclif command —--agent-type,--pr-created,--review-submittedare legitimate CLI extensions, listed in--help. Not a real bug. The pin in this PR is preventive, not a fix for that specific run.End-to-end smoke (built CLI)
Stdout is envelope-only; operator observability via the engine log file preserved.
Test plan
npm test— 9057 / 9057 passingtests/unit/cli/cascade-tools-help.test.ts(NEW, 2 tests) — pins canonical topic summaries + per-gadget --help unaffectedtests/unit/cli/cascade-tools-stdout-cleanliness.test.ts(NEW, 3 tests) — pins envelope-only stdout under worker / standalone / dev-override env shapes; pins engine log gets logger outputtests/unit/cli/cli-command-factory.test.ts(extended, 1 new test) — pins unknown-flag envelope on factory-generated commands (locks the strict-mode default)npm run lintclean (only pre-existing complexity warnings in unrelated files)npm run typecheckcleanFiles changed
bin/cascade-tools.js—+48lines (topics block + LLMIST_LOG_TEE strip + LLMIST_LOG_FILE fallback)src/cli/base.ts—+9lines (one-linestatic override strict = trueplus rationale)tests/unit/cli/cascade-tools-help.test.ts—+80lines (NEW, regression net for topics)tests/unit/cli/cascade-tools-stdout-cleanliness.test.ts—+124lines (NEW, regression net for stdout cleanliness)tests/unit/cli/cli-command-factory.test.ts—+26lines (strict-mode regression test)🤖 Generated with Claude Code