Skip to content
This repository was archived by the owner on Jun 8, 2026. It is now read-only.

fix: DECOMPOSE and REQUEST_CHANGES parity with run-loop.sh - #33

Merged
thadeusb merged 4 commits into
mainfrom
fix/decompose-request-changes-parity
Mar 18, 2026
Merged

thadeusb merged 4 commits into
mainfrom
fix/decompose-request-changes-parity

Conversation

@thadeusb

Copy link
Copy Markdown
Contributor

Summary

  • DECOMPOSE: added -p, --output-format stream-json, --verbose, --allowedTools, --max-turns 200
  • REQUEST_CHANGES: added -p, --output-format stream-json, --verbose (tools/max-turns were already there)
  • Both now pipe through stream_formatter.py for human-readable log output (same pipeline as run-loop.sh)
  • Removed broken settings.local.json write — .claude/ is a hardcoded protected path in Claude Code, allow rules don't override it

Root cause

DECOMPOSE and REQUEST_CHANGES were spawned with minimal args copied from the ECS harness's buildClaudeDirectArgs(), which builds args separately from the spawn site. Missing: -p (headless), --output-format stream-json (streaming logs), --verbose, tool permissions (DECOMPOSE), max-turns (DECOMPOSE).

Test plan

  • Run REQUEST_CHANGES — verify formatted log output streams to symphony-loop.log
  • Run DECOMPOSE — verify formatted output, tool permissions work for writing features.json
  • Verify formatter fallback works if code plugin not installed (raw output instead of crash)

Both commands now match the run-loop.sh spawn pattern:
- -p flag for headless mode (permissions without prompting)
- --output-format stream-json for streaming log output
- --verbose for detailed logging
- --allowedTools with full tool set
- --max-turns 200

Output is piped through stream_formatter.py (same pipeline as
run-loop.sh) for human-readable logs instead of raw JSON.

Also removes the settings.local.json write that attempted to grant
Edit/Write permissions for .claude/work/ — this doesn't work because
.claude/ is a hardcoded protected path in Claude Code.
* Find the stream_formatter.py script from the code plugin.
* Falls back to null if not installed — caller should degrade gracefully.
*/
function findStreamFormatter(): string | null {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[MEDIUM] Code Quality

[P2] findStreamFormatter duplicates plugin-cache utilities already exported by plugin-cache.ts

Recommendation: Rewrite findStreamFormatter to call getPluginCacheRoot() and findPluginVersions() from plugin-cache.ts, then iterate over versions looking for {version}/tools/python/stream_formatter.py. Alternatively, add a findPluginTool(pluginName, toolSubPath) helper to plugin-cache.ts that accepts an arbitrary sub-path.

const cacheRoot = path.join(os.homedir(), ".claude", "plugins", "cache", "closedloop-ai", "code");
  try {
    const versions = readdirSync(cacheRoot)
      .filter((e: string) => /^\d+\.\d+\.\d+/.test(e))
      .sort((a: string, b: string) => { ... });

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed — now uses getPluginCacheRoot() and findPluginVersions() from plugin-cache.ts. Removed the duplicated semver sort and readdirSync import.

@@ -1013,22 +1080,6 @@ async function handleLoopRequest(
claudeWorkDir = path.join(worktreeDir, ".claude", "work");
await fs.mkdir(claudeWorkDir, { recursive: true });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[HIGH] Correctness

[P1] Removal of settings.local.json permission grant breaks writes to .claude/work/

Recommendation: Re-add the settings.local.json permission grant before the command dispatch, or add an alternative mechanism to permit writes to .claude/work/**. If the behavior was intentionally removed because a newer Claude Code version no longer needs it, add a comment explaining that.

claudeWorkDir = path.join(worktreeDir, ".claude", "work");
await fs.mkdir(claudeWorkDir, { recursive: true });
// <-- settings.local.json permission grant was removed here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Intentional removal. We verified that settings.local.json allow rules do NOT override the .claude/ sensitive path protection — it's hardcoded in Claude Code (even bypassPermissions mode still prompts for .claude/ writes, per docs). The plugin already works around this by routing file modifications through Bash(cat/sed/python) instead of the Edit/Write tools. The work directory will move to .closedloop-ai/work/ in a future change to bypass this entirely.

try {
const versions = readdirSync(cacheRoot)
.filter((e: string) => /^\d+\.\d+\.\d+/.test(e))
.sort((a: string, b: string) => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[MEDIUM] Code Quality

[P2] findStreamFormatter duplicates compareSemverDescending from plugin-cache.ts

Recommendation: Replace the inline sort with compareSemverDescending and the version scan with findPluginVersions, both already imported from ./plugin-cache.js. The only local logic needed is the tools/python/stream_formatter.py path construction.

.sort((a: string, b: string) => {
  const pa = a.split(".").map(Number);
  const pb = b.split(".").map(Number);
  for (let i = 0; i < 3; i++) {
    const diff = (pb[i] ?? 0) - (pa[i] ?? 0);
    if (diff !== 0) { return diff; }
  }
  return 0;
});

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in same commit as comment #1 — findStreamFormatter now uses getPluginCacheRoot() and findPluginVersions() from plugin-cache.ts. No more inline semver sort.

@closedloop-ai-stage

Copy link
Copy Markdown

Code Review Summary

Status: Needs Attention

Reviewers: Bug Hunter A, Bug Hunter B, Unified Auditor, Premise Reviewer, gateway-core-architect

Findings

Severity Count
Blocking 0
High 1
Medium 2

HIGH Issues (should fix)

  1. [P1] [symphony-loop.ts:1081] Removal of settings.local.json permission grant breaks writes to .claude/work/

MEDIUM Issues (consider)

  1. [P2] [symphony-loop.ts:112] findStreamFormatter duplicates plugin-cache utilities already exported by plugin-cache.ts
  2. [P2] [symphony-loop.ts:117] findStreamFormatter duplicates compareSemverDescending from plugin-cache.ts

Validation Stats

  • Agent failures: 0 partitions skipped
  • Cross-file grouped: 0 findings consolidated
  • Duplicates merged: 2

Recommendation: Address the HIGH issue — the removal of the settings.local.json permission grant may break headless writes to .claude/work/. The MEDIUM findings are DRY violations in the new findStreamFormatter function that can be resolved by using existing exports from plugin-cache.ts.

@thadeusb
thadeusb merged commit f47a1b5 into main Mar 18, 2026
2 checks passed
@thadeusb
thadeusb deleted the fix/decompose-request-changes-parity branch March 18, 2026 19:50
aponamarev added a commit that referenced this pull request Mar 20, 2026
…_CHANGES

PR #30 branched before PRs #31-#33 landed, leaving all three commands on
the old spawnClaudeFromFile path (--output-format json, no streaming, no
token tracking, no --allowedTools, no --max-turns). This commit brings
parity with the fixes merged to main:

- Add findStreamFormatter() and buildClaudePipeline() (ported from main)
- DECOMPOSE: switch from spawnClaudeFromFile to buildClaudePipeline with
  -p -, --output-format stream-json, --verbose, --allowedTools, --max-turns 200
- EVALUATE_PRD: same treatment — produces claude-output.jsonl for token
  tracking, claude-stderr.log for debugging, and readable formatted logs
- REQUEST_CHANGES: add -p, --output-format stream-json, --verbose, and
  route through buildClaudePipeline (was spawning claude directly)
- Remove now-dead spawnClaudeFromFile helper

Testing: `just desktop-typecheck` clean; all 242 tests pass
Risks: None — behaviour change is intentional parity fix; formatter falls
       back gracefully when stream_formatter.py is not installed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant