Skip to content

release: v6.9.6 - #66

Merged
code-crusher merged 2 commits into
mainfrom
release/v6.9.6
Oct 6, 2026
Merged

code-crusher merged 2 commits into
mainfrom
release/v6.9.6

Conversation

@code-crusher

Copy link
Copy Markdown
Member

Summary

Headless mode (orbcode -p) gets features for programmatic use:

  • --json: prints exactly one JSON envelope to stdout: { ok, model, result, usage, sessionId, error }. Everything else goes to stderr.
  • --baseUrl / --apiKey: route through any OpenAI-compatible endpoint instead of the MatterAI gateway. They use dedicated MATTERAI_LLM_* env names so they don't clash with the gateway's MATTERAI_BASE_URL / MATTERAI_API_KEY. The model defaults to gpt-4o when --model isn't given.
  • --require-model: exits non-zero on an unknown model instead of silently using the default. This is automatic with --json.
  • --output-file <path>: the agent writes its final result to this file, and headless mode reads it back. Writes to that exact path are auto-approved even without --yolo.
  • --verbose: streams tool-start/tool-end events to stderr.

Version bumped to 6.9.6, and the changelog is updated.

Fixes made during release validation

  • usage.totalCost summed the agent's running total on every usage event, so it over-counted. It now takes the latest value.
  • The help text and changelog said --baseUrl falls back to OPENAI_API_KEY / ANTHROPIC_API_KEY from the environment. The OpenAI-compatible transport doesn't do that, so the claim is removed.

Validation

  • npm run typecheck and npm run build pass
  • All node test suites pass (attachments, mcp, plugins, metrics, json-repair, readonly, search, rewind, plus the new headless suite), and test:ui passes 21/21
  • Smoke tests on the built CLI:
    • --json --model <unknown> exits 1 with a clear error
    • --json --baseUrl http://127.0.0.1:1/v1 prints a valid {"ok":false,…} envelope on stdout and exits 1

@matterai-app

matterai-app Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Code Quality

Summary By MatterAI MatterAI logo

🔄 What Changed

  • Added isOutputFileWrite helper in src/headless.ts to securely validate and auto-approve file writes targeting the designated output file.
  • Implemented stream flushing (flush) prior to process termination to prevent truncated piped output when running in headless mode.
  • Refactored unit tests in test/headless.test.ts to thoroughly validate output file path matching and restrictions.

🔍 Impact of the Change

  • Ensures reliable piping for automated CLI workflows (e.g., --json | jq) by safely flushing output and error streams before exit.
  • Enhances security by tightly scoping auto-approval permissions solely to the explicit output file path.

📁 Total Files Changed

Click to Expand
File ChangeLog
Headless Logic src/headless.ts Added isOutputFileWrite validation and stream flushing for safe process exit.
CLI Routing src/index.tsx Removed unused flag fallback code.
Unit Tests test/headless.test.ts Streamlined tests for output file write validations and path normalization.

🧪 Test Added/Recommended

Added

  • test/headless.test.ts: Added assertions covering strict path resolution, non-file tools denial, and missing output file scenarios.

🔒 Security Vulnerabilities

  • None detected. Path traversal and prefix spoofing are mitigated using path.resolve checks.

⏳ Estimated code review effort

LOW (~7 minutes)

Tip

Quality Recommendations

  1. Ensure async stream flushing errors are caught gracefully if stream writing fails.

♫ Tanka Poem

Streams drain to the pipe,
Output path securely signed,
Code flows clear and bright.
Tests confirm the strict design,
Version six point nine and nine.

Sequence Diagram

sequenceDiagram
    participant U as User
    participant CLI as runHeadless
    participant A as Agent
    participant FS as File System
    
    U->>CLI: orbcode -p prompt --output-file out/result.json
    CLI->>A: execute(prompt, options)
    A->>CLI: requestApproval(toolName, detail)
    CLI->>CLI: isOutputFileWrite(outputFile, toolName, detail)
    alt Matches Output File
        CLI-->>A: "yes" (auto-approved)
        A->>FS: Write to out/result.json
    else Other Tool / Path
        CLI-->>A: "no" (denied)
    end
    A-->>CLI: completion
    CLI->>CLI: flush(stdout) & flush(stderr)
    CLI-->>U: Exit process
Loading

@matterai-app matterai-app Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧪 PR Review is completed: Headless JSON envelope, --output-file artifact, and custom-endpoint support are well built; flagging a MATTERAI_MODEL env clobber in the --baseUrl default, a stdout-flush race before process.exit in --json mode, a fragile exact-path match in the output-file approval bypass, and tautological tests that never exercise the production approval logic. package.json: clean version bump + test script.

Skipped files
  • CHANGELOG.md: Skipped file pattern
  • package-lock.json: Skipped file pattern
⬇️ Low Priority Suggestions (3)
src/headless.ts (2 suggestions)

Location: src/headless.ts (Lines 302-302)

🟡 Reliability

Issue: Node's docs warn that process.exit() truncates pending asynchronous stdout writes, and piped stdout is asynchronous on macOS. The new --json envelope write happens immediately before the unconditional process.exit(exitCode) at the end of runHeadless, and --json is precisely the mode designed to be piped (orbcode -p … --json | jq) — with a large result the envelope can be intermittently truncated or lost.

Fix: Exit from the write callback so the envelope is flushed before the process terminates. (This intentionally skips the stderr resume hint in JSON mode; sessionId is already in the envelope.)

Impact: Guarantees the JSON envelope is delivered on all platforms; eliminates intermittent empty/truncated output when piping.

-  		process.stdout.write(JSON.stringify(envelope) + "\n")
+  		process.stdout.write(JSON.stringify(envelope) + "\n", () => process.exit(exitCode))
+  		return

Location: src/headless.ts (Lines 248-248)

🟡 Robustness

Issue: The auto-approval compares the model's raw file_path (request.detail) against the resolved output path with strict string equality. Models routinely relativize paths or add ./ despite the prompt instruction; in that case the write is denied and --output-file silently degrades to the chat-text fallback that this PR's own comments call unreliable for weaker models.

Fix: Resolve request.detail before comparing — result.json, ./result.json, and trailing-slash variants now match the same file, while different targets (result.json.bak, traversal segments) still resolve to a different path and stay denied. The request.detail && guard keeps an empty path from resolving to cwd().

Impact: The output-file artifact and its approval bypass work regardless of how the model formats the path, without widening what gets auto-approved.

-  				if (outputFile && request.toolName === "file_write" && request.detail === path.resolve(outputFile)) {
+  			if (outputFile && request.toolName === "file_write" && request.detail && path.resolve(request.detail) === path.resolve(outputFile)) {
test/headless.test.ts (1 suggestion)

Location: test/headless.test.ts (Lines 23-34)

🟡 Test Quality

Issue: These tests never exercise the production code. They restate the condition inline with literal comparisons — "file_write" === "file_write" (always true), "execute_command" === "file_write" (always false), undefined !== undefined (always false) — and the "JSON envelope structure" tests assert properties of an object they just constructed. They pass even if runHeadless's approval logic regresses (e.g., reverts to substring matching), giving false confidence on a security-relevant auto-approval path.

Fix: Extract the approval predicate in src/headless.ts into an exported shouldApproveOutputFile(outputFile, toolName, detail) helper used by the requestApproval callback, import it here, and test the real function across all cases; drop the self-asserting envelope tests.

Impact: The tests actually pin the bypass behavior — a regression in headless.ts now fails CI instead of silently passing.

-  	it("should approve file_write to the exact output file path", () => {
-  		const resolvedOutput = path.resolve(outputFile)
-  		const requestDetail = resolvedOutput
-  
-  		// This is the logic from headless.ts requestApproval callback
-  		const shouldApprove =
-  			outputFile !== undefined &&
-  			"file_write" === "file_write" &&
-  			requestDetail === path.resolve(outputFile)
-  
-  		assert.strictEqual(shouldApprove, true, "Should approve exact match")
-  	})
+  	it("should approve file_write to the exact output file path", () => {
+  		const requestDetail = path.resolve(outputFile)
+  
+  		assert.strictEqual(
+  			shouldApproveOutputFile(outputFile, "file_write", requestDetail),
+  			true,
+  			"Should approve exact match",
+  		)
+  	})

Comment thread src/index.tsx Outdated
- Don't overwrite MATTERAI_MODEL when --baseUrl is given without --model;
  runHeadless already defaults to gpt-4o.
- Flush stdout/stderr before process.exit so piped --json output isn't
  truncated on macOS.
- Resolve the file_write path before matching --output-file, so relative
  spellings are auto-approved while other targets stay denied.
- Extract isOutputFileWrite and test the real predicate instead of
  restated logic.
@matterai-app

matterai-app Bot commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

✅ Reviewed the changes: Clean follow-up — all four prior review comments are addressed: the MATTERAI_MODEL-clobbering default block is deleted, both stdio streams are flushed before process.exit, the output-file approval is extracted into a path-resolving isOutputFileWrite helper, and the tests now exercise the real function. Reviewed src/headless.ts: no issues found. Reviewed test/headless.test.ts: no issues found. Reviewed src/index.tsx: no issues found.

@code-crusher
code-crusher merged commit e8563bc into main Oct 6, 2026
1 check passed
@code-crusher
code-crusher deleted the release/v6.9.6 branch October 6, 2026 11:09
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.

1 participant