feat: Commit, Push and Create PR actions - #13

Merged
juliusmarminge merged 18 commits into
mainfrom
codex/add-github-commit-push-ui
Feb 12, 2026
Merged

feat: Commit, Push and Create PR actions#13
juliusmarminge merged 18 commits into
mainfrom
codex/add-github-commit-push-ui

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Feb 12, 2026

Copy link
Copy Markdown
Member

Open with Devin

Summary by CodeRabbit

  • New Features

    • AI-powered generation for commit messages and PR title/body.
    • New Git core & manager with stacked workflows (commit, commit & push, commit & push & create/open PR), richer status details, and WebSocket/native endpoints to run Git status and actions.
    • UI: Git actions menu with real-time status, action execution, notices and error handling.
  • Chores

    • Robust process runner with buffer and timeout safeguards.
  • Tests

    • Extensive end-to-end and unit tests covering Git flows, PR lifecycle, manager behavior, and contract schemas.

@coderabbitai

coderabbitaiBot commented Feb 12, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds a Git subsystem: process runner with buffer/timeouts, Git core service, GitManager orchestration, Codex-based commit/PR generation, WS API + server wiring, client UI integration, contracts/schemas for stacked git actions, and extensive tests.

Changes

Cohort / File(s)Summary
Text Generation Service
apps/server/src/coreServices.ts, apps/server/src/codexTextGenerator.ts
Defines TextGenerationService types and implements CodexTextGenerator with JSON schemas, validation/parsers, temp-file helpers, runCodexJson orchestration, and methods to generate commit messages and PR content.
Process Execution
apps/server/src/processRunner.ts
Adds runProcess with configurable maxBufferBytes, stdout/stderr accumulation and byte limits, timeout handling (SIGTERM→SIGKILL), spawn/exit normalization, and detailed error types.
Git Core Service
apps/server/src/git.ts
Introduces GitCoreService and helpers: enriched status/statusDetails, prepareCommitContext, commit, pushCurrentBranch, readRangeContext, readConfigValue, runGit helpers, and upstream/default-branch logic.
GitManager Orchestration
apps/server/src/gitManager.ts
Adds GitManager coordinating gitCore, processRunner, Codex text generation, and gh interactions for stacked actions (commit, push, PR), PR discovery/creation, temp-file PR bodies, and error normalization.
Server WS Integration & Tests
apps/server/src/wsServer.ts, apps/server/src/wsServer.test.ts
Wires GitManager into server options, exposes WS methods git.status and git.runStackedAction, and updates tests to inject/validate gitManager delegation and error propagation.
Tests: GitManager & GitCore
apps/server/src/gitManager.test.ts, apps/server/src/git.test.ts
Adds end-to-end tests covering stacked actions, GH CLI scenarios, upstream behavior, detached HEAD, auth errors, and GitCoreService behaviors.
Client Integration
apps/web/src/wsNativeApi.ts, apps/web/src/components/ChatView.tsx
Extends native API with git.status and git.runStackedAction; integrates Git actions menu and UI state into ChatView with status polling, action execution, and notices.
Contracts / IPC / WS Methods & Tests
packages/contracts/src/git.ts, packages/contracts/src/git.test.ts, packages/contracts/src/ipc.ts, packages/contracts/src/ws.ts
Adds Zod schemas/types for git status and stacked actions, updates NativeApi.git signatures, expands WS_METHODS with git.status and git.runStackedAction, and adds schema tests.

Sequence Diagram(s)

sequenceDiagram
participant Client as Client (ChatView)
participant WS as WebSocket Server
participant GM as GitManager
participant GC as GitCoreService
participant Proc as ProcessRunner
participant Codex as Codex Service
participant GH as GitHub CLI
Client->>WS: git.runStackedAction(action, cwd)
WS->>GM: runStackedAction()
rect rgba(100,150,200,0.5)
Note over GM: Commit Step
GM->>GC: prepareCommitContext(cwd)
GC-->>GM: stagedSummary, stagedPatch
GM->>Codex: generateCommitMessage(diff)
Codex-->>GM: subject, body
GM->>Proc: git commit -m "..."
Proc-->>GM: commit result
end
rect rgba(100,200,150,0.5)
Note over GM: Push Step (if requested)
GM->>GC: pushCurrentBranch(cwd, upstream?)
GC-->>GM: push result
end
rect rgba(200,150,100,0.5)
Note over GM: PR Step (if requested)
GM->>GC: readRangeContext(base, head)
GC-->>GM: commitSummary, diffSummary, diffPatch
GM->>Codex: generatePrContent(rangeContext)
Codex-->>GM: title, body
GM->>GH: gh pr list --head branch
GH-->>GM: existing PRs
alt PR exists
GM->>GH: gh pr view PR_NUMBER
GH-->>GM: PR details
else
GM->>GH: gh pr create --title "..." --body file://tmp
GH-->>GM: new PR info
end
end
GM-->>WS: GitRunStackedActionResult
WS-->>Client: result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 2.56% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title 'feat: Commit, Push and Create PR actions' accurately captures the main feature added: three new Git workflow actions integrated into the UI and backend services.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch codex/add-github-commit-push-ui

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps

greptile-appsBot commented Feb 12, 2026

Copy link
Copy Markdown

Greptile Overview

Greptile Summary

This PR implements comprehensive Git stacked actions (commit, push, create PR) with AI-powered text generation for commit messages and PR content. The implementation spans server-side orchestration, robust process execution, WebSocket/IPC integration, and a polished React UI with real-time progress tracking.

Key changes:

  • New GitManager orchestrates stacked workflows (commit → push → PR) with proper error handling and GitHub CLI integration
  • GitCoreService provides low-level Git operations using direct spawn (no shell), with buffer management and timeout safeguards
  • CodexTextGenerator generates commit messages and PR content via Codex CLI with structured JSON output schemas
  • ProcessRunner implements robust subprocess execution with truncation support, graceful termination, and buffer limit enforcement
  • GitActionsControl UI component provides modal-based workflow with step-by-step progress, custom commit messages, and error states
  • Comprehensive test coverage across unit tests (git.test.ts, processRunner.test.ts) and integration tests (gitManager.test.ts)
  • WebSocket and IPC layers extended to expose git.status and git.runStackedAction methods to both web and desktop clients

Architecture aligns with project priorities:

  • Performance: Direct spawn without shell overhead, buffer limits prevent memory issues
  • Reliability: Proper timeout handling, graceful degradation (PR lookup is best-effort), temp file cleanup
  • Predictable behavior: Structured status tracking, deterministic base branch resolution, comprehensive error normalization

Confidence Score: 5/5

  • This PR is safe to merge with high confidence
  • The implementation demonstrates strong engineering discipline: comprehensive test coverage (490 lines in gitManager.test.ts alone), proper error handling with normalized error messages, robust subprocess management with buffer limits and timeouts, dependency injection for testability, and alignment with project priorities (performance, reliability, predictable behavior). The only noted issue is duplicate commit message sanitization which is cosmetic and doesn't affect functionality.
  • No files require special attention

Important Files Changed

FilenameOverview
apps/server/src/git.tsAdds comprehensive Git operations with robust error handling, proper spawn arguments, and buffer management for status, commit, push, and range context operations
apps/server/src/gitManager.tsImplements high-level orchestration for stacked Git workflows (commit, push, PR) with AI text generation, proper dependency injection, and GitHub CLI integration
apps/server/src/codexTextGenerator.tsImplements AI-powered text generation for commit messages and PR content using Codex CLI with structured JSON output, proper sanitization, and temp file cleanup
apps/server/src/processRunner.tsAdds robust process execution with buffer limit enforcement, timeout handling, graceful termination (SIGTERM then SIGKILL), and truncation support
apps/server/src/wsServer.tsIntegrates GitManager into WebSocket server to expose git.status and git.runStackedAction methods via WS protocol
packages/contracts/src/git.tsExtends Git contracts with Zod schemas for stacked action API (status, commit, push, PR) with comprehensive validation rules
apps/web/src/components/GitActionsControl.tsxImplements comprehensive Git actions UI with modal workflow, real-time progress tracking, step-by-step execution, error handling, and PR link opening

Sequence Diagram

sequenceDiagram
participant User
participant GitActionsControl
participant NativeApi
participant GitManager
participant GitCore
participant CodexTextGenerator
participant GitCLI
participant GitHubCLI
User->>GitActionsControl: Click "Commit and create PR"
GitActionsControl->>GitActionsControl: Open modal, set action
User->>GitActionsControl: Confirm action
GitActionsControl->>NativeApi: git.runStackedAction(commit)
NativeApi->>GitManager: runStackedAction(commit)
GitManager->>GitCore: statusDetails(cwd)
GitCore->>GitCLI: git status --porcelain=2
GitCLI-->>GitCore: status output
GitCore-->>GitManager: branch, upstream info
GitManager->>GitCore: prepareCommitContext(cwd)
GitCore->>GitCLI: git add -A
GitCore->>GitCLI: git diff --cached
GitCLI-->>GitCore: staged changes
GitCore-->>GitManager: stagedSummary, stagedPatch
GitManager->>CodexTextGenerator: generateCommitMessage()
CodexTextGenerator->>CodexTextGenerator: Write temp schema file
CodexTextGenerator->>GitCLI: codex exec --output-schema
GitCLI-->>CodexTextGenerator: JSON response
CodexTextGenerator->>CodexTextGenerator: Parse, sanitize, cleanup
CodexTextGenerator-->>GitManager: {subject, body}
GitManager->>GitCore: commit(cwd, subject, body)
GitCore->>GitCLI: git commit -m subject -m body
GitCLI-->>GitCore: success
GitCore-->>GitManager: {commitSha}
GitManager-->>NativeApi: commit result
NativeApi-->>GitActionsControl: Update progress: commit completed
GitActionsControl->>NativeApi: git.runStackedAction(commit_push)
NativeApi->>GitManager: runStackedAction(commit_push)
GitManager->>GitCore: pushCurrentBranch(cwd)
GitCore->>GitCLI: git push -u origin branch
GitCLI-->>GitCore: success
GitCore-->>GitManager: {status: pushed, branch}
GitManager-->>NativeApi: push result
NativeApi-->>GitActionsControl: Update progress: push completed
GitActionsControl->>NativeApi: git.runStackedAction(commit_push_pr)
NativeApi->>GitManager: runStackedAction(commit_push_pr)
GitManager->>GitHubCLI: gh pr list --head branch
GitHubCLI-->>GitManager: [] (no existing PR)
GitManager->>GitCore: readRangeContext(cwd, baseBranch)
GitCore->>GitCLI: git log, git diff
GitCLI-->>GitCore: commit history, diff
GitCore-->>GitManager: rangeContext
GitManager->>CodexTextGenerator: generatePrContent()
CodexTextGenerator->>GitCLI: codex exec --output-schema
GitCLI-->>CodexTextGenerator: JSON response
CodexTextGenerator-->>GitManager: {title, body}
GitManager->>GitHubCLI: gh pr create --title --body-file
GitHubCLI-->>GitManager: PR URL
GitManager->>GitHubCLI: gh pr view --web
GitHubCLI-->>GitManager: success
GitManager-->>NativeApi: pr result
NativeApi-->>GitActionsControl: Update progress: PR created
GitActionsControl->>User: Show completion with PR link
Loading

Last reviewed commit: dd92084

@greptile-appsgreptile-appsBot 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.

14 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment threadapps/server/src/gitManager.ts

@coderabbitaicoderabbitaiBot 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.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@apps/server/src/processRunner.ts`:
- Around line 128-131: Attach an 'error' listener to the child's stdin and use
the write callback to handle possible write errors before calling end: in
processRunner.ts, before calling child.stdin.write(...) register
child.stdin.once("error", err => {/* reject/forward error or cleanup and ensure
promise rejects */}), call child.stdin.write(options.stdin, (err) => { if (err)
{ /* handle/reject/cleanup */ } child.stdin.end(); }); and remove the
unconditional child.stdin.end() so we only end after the write completes; ensure
any error handling forwards the error to the same rejection/cleanup path used by
child.once("error") for the spawned process.
🧹 Nitpick comments (2)
packages/contracts/src/git.test.ts (1)

11-71: LGTM!

The tests provide good coverage for schema validation, including whitespace trimming and nested field parsing.

Consider adding negative test cases to verify that invalid inputs are rejected (e.g., invalid action strings, missing required fields). This would strengthen the contract validation.

,

apps/server/src/codexTextGenerator.ts (1)

84-103: Consider consolidating duplicate sanitization logic.

sanitizeCommitSubject here (lines 84-95) and sanitizeCommitMessage in gitManager.ts (lines 99-110) perform nearly identical operations: extracting the first line, removing trailing periods, and truncating to 72 characters. This duplication could lead to divergent behavior over time.

Consider extracting a shared utility or having gitManager.ts rely on the already-sanitized output from CodexTextGenerator without additional sanitization.

Comment threadapps/server/src/processRunner.ts
Comment threadapps/server/src/gitManager.ts Outdated
Comment threadapps/server/src/processRunner.ts
Comment threadapps/server/src/processRunner.ts
Comment threadapps/server/src/wsServer.ts
Comment threadapps/server/src/gitManager.ts Outdated
Comment threadapps/web/src/components/ChatView.tsx Outdated
Comment threadapps/server/src/codexTextGenerator.ts Outdated
@macroscopeapp

macroscopeappBot commented Feb 12, 2026

Copy link
Copy Markdown
Contributor

Add Commit, Push, and Create PR actions by introducing GitCoreService, GitManager.runStackedAction, and a web GitActionsControl wired through WS and IPC APIs

Implements stacked git workflows across server and web: adds GitCoreService for git operations, CodexTextGenerator for commit/PR text, and GitManager orchestration; exposes git.status and git.runStackedAction over WebSocket; adds desktop bridge shell.openExternal; and introduces a GitActionsControl UI to run commit/push/PR. Terminal spawning gains multi-shell fallback and runProcess provides standardized subprocess handling. See apps/server/src/gitManager.ts, apps/server/src/git.ts, apps/server/src/codexTextGenerator.ts, and apps/web/src/components/GitActionsControl.tsx.

📍Where to Start

Start with the orchestration entrypoint GitManager.runStackedAction in apps/server/src/gitManager.ts, then review GitCoreService in apps/server/src/git.ts and the web client GitActionsControl in apps/web/src/components/GitActionsControl.tsx.


Macroscope summarized dd92084.

Co-authored-by: codex <codex@users.noreply.github.com>
Comment threadapps/server/src/gitManager.ts

@coderabbitaicoderabbitaiBot 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.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@apps/web/src/components/ChatView.tsx`:
- Around line 395-424: When a git status fetch succeeds we need to clear any
previous error so the error banner doesn't persist; inside the load async
function (the one that calls api.git.status with gitCwd) after successfully
calling setGitStatus(nextStatus) also call setGitActionError(null) (guarded by
the same !cancelled check) so successful refreshes remove stale errors; update
the useEffect's load success branch in ChatView.tsx (the load function /
useEffect that references api, gitCwd, setGitStatus, setGitActionError)
accordingly.

Comment threadapps/web/src/components/ChatView.tsx Outdated
Comment threadapps/server/src/git.ts
Comment threadapps/server/src/git.ts Outdated

@coderabbitaicoderabbitaiBot 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.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@apps/server/src/git.test.ts`:
- Around line 460-466: The test invokes git with a single-quoted remote path
which fails on Windows; update the call that runs git(tmp.path, `remote add
origin '${remote.path}'`) to wrap the path using JSON.stringify(remote.path)
(i.e., produce a double-quoted, escaped string) so Windows cmd handles spaces
correctly; modify the test where makeTmpDir()/remote and the git(...) invocation
are used (see git(tmp.path, `remote add origin ...`), initRepoWithCommit,
createGitBranch) to pass the JSON.stringify-wrapped path instead of single
quotes.
In `@apps/server/src/git.ts`:
- Around line 245-251: The current early-return treats a branch as
"skipped_up_to_date" when details.hasUpstream && details.aheadCount === 0 even
if it is behind; update the condition to also require details.behindCount === 0
so we only mark truly up-to-date branches. Change the if that checks
details.hasUpstream and details.aheadCount to: details.hasUpstream &&
details.aheadCount === 0 && details.behindCount === 0 (leaving the returned
object with branch and optional upstreamBranch unchanged).
- Around line 129-143: The timeout check must be unconditional: in
runGitOrThrow, always throw when result.timedOut by calling
normalizeGitExecutionError(args, result) (or similar) before considering
options.allowNonZeroExit; then keep the existing non-zero exit handling for
result.code when options.allowNonZeroExit is false. Update the logic in
runGitOrThrow (referencing runGitOrThrow, RunGitOptions,
options.allowNonZeroExit, result.timedOut, result.code, and
normalizeGitExecutionError) so timeouts are detected and thrown unconditionally
while allowing suppressed non-zero exit codes only when appropriate.
In `@apps/server/src/gitManager.ts`:
- Around line 402-423: Both runGh and runGhStdout currently call this.run("gh",
args, { cwd }) and can hang; add an explicit timeoutMs option to those calls.
Define a clear constant (e.g. GH_CLI_TIMEOUT_MS = 30_000) near the top of the
module and pass it into this.run as { cwd, timeoutMs: GH_CLI_TIMEOUT_MS } in
both runGh and runGhStdout so gh CLI invocations time out predictably. Ensure
the constant is used in both functions and adjust any types if needed to match
ProcessRunOptions.
🧹 Nitpick comments (1)
apps/server/src/gitManager.ts (1)

175-183: If no commit is created, skip push/PR to avoid empty actions.

When runCommitStep returns skipped_no_changes, the current flow still pushes and can attempt PR creation. That can create confusing “no‑op” PRs or unnecessary network calls.

Comment threadapps/server/src/git.test.ts
Comment threadapps/server/src/git.ts
Comment threadapps/server/src/git.ts Outdated
Comment threadapps/server/src/gitManager.ts
@juliusmarmingejuliusmarminge changed the title Add stacked GitHub action workflowGithubFeb 12, 2026
@juliusmarmingejuliusmarminge changed the title Githubfeat: Commit, Push and Create PR actionsFeb 12, 2026
Co-authored-by: codex <codex@users.noreply.github.com>
Comment on lines +123 to +128
const fail = (error: Error): void => {
child.kill("SIGTERM");
finalize(() => {
reject(error);
});
};

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.

🟡 Medium

src/processRunner.ts:123 The fail function clears the timeout that would escalate to SIGKILL, so if the child ignores SIGTERM, the parent hangs forever. Consider adding a force-kill timer in fail similar to the timeout logic (lines 108-110).

- const fail = (error: Error): void => {- child.kill("SIGTERM");- finalize(() => {- reject(error);- });+ const fail = (error: Error): void => {+ child.kill("SIGTERM");+ const killTimer = setTimeout(() => {+ child.kill("SIGKILL");+ }, 1_000);+ finalize(() => {+ clearTimeout(killTimer);+ reject(error);+ });

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/processRunner.ts around lines 123-128:
The `fail` function clears the timeout that would escalate to `SIGKILL`, so if the child ignores `SIGTERM`, the parent hangs forever. Consider adding a force-kill timer in `fail` similar to the timeout logic (lines 108-110).

Comment threadapps/server/src/processRunner.ts Outdated
stream: "stdout" | "stderr",
chunk: Buffer | string,
): Error | null => {
const text = chunk.toString();

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.

🟢 Low

src/processRunner.ts:134 Using chunk.toString() per Buffer can split multi‑byte UTF‑8 and corrupt text; consider StringDecoder to assemble stdout/stderr safely.

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/processRunner.ts around line 134:
Using `chunk.toString()` per `Buffer` can split multi‑byte UTF‑8 and corrupt text; consider `StringDecoder` to assemble `stdout`/`stderr` safely.

@coderabbitaicoderabbitaiBot 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/server/src/git.ts (1)

50-86: ⚠️ Potential issue | 🟠 Major

Add output caps to avoid unbounded git command buffering.
runGit accumulates stdout/stderr without limits; large diffs can spike memory and make behavior unpredictable under load. Consider enforcing a max buffer (similar to processRunner) or streaming to disk with a safe cap.

🔧 Suggested fix (bounded output)
+const DEFAULT_GIT_MAX_BUFFER_BYTES = 8 * 1024 * 1024;+-function runGit(args: readonly string[], cwd: string, timeoutMs = 30_000): Promise<TerminalCommandResult> {+function runGit(+ args: readonly string[],+ cwd: string,+ timeoutMs = 30_000,+ maxBufferBytes = DEFAULT_GIT_MAX_BUFFER_BYTES,+): Promise<TerminalCommandResult> {
return new Promise((resolve, reject) => {
const child = spawn("git", args, {
cwd,
env: process.env,
stdio: ["ignore", "pipe", "pipe"],
});
let stdout = "";
let stderr = "";
let timedOut = false;
+ let stdoutBytes = 0;+ let stderrBytes = 0;+ let settled = false;
const timeout = setTimeout(() => {
timedOut = true;
child.kill("SIGTERM");
setTimeout(() => {
if (!child.killed) child.kill("SIGKILL");
}, 1_000).unref();
}, timeoutMs);
+ const fail = (error: Error) => {+ if (settled) return;+ settled = true;+ clearTimeout(timeout);+ child.kill("SIGTERM");+ reject(error);+ };+
child.stdout?.on("data", (chunk: Buffer) => {
- stdout += chunk.toString();+ const text = chunk.toString();+ stdout += text;+ stdoutBytes += Buffer.byteLength(text);+ if (stdoutBytes > maxBufferBytes) {+ fail(+ new Error(+ `${commandLabel(args)} exceeded stdout buffer limit (${maxBufferBytes} bytes).`,+ ),+ );+ }
});
child.stderr?.on("data", (chunk: Buffer) => {
- stderr += chunk.toString();+ const text = chunk.toString();+ stderr += text;+ stderrBytes += Buffer.byteLength(text);+ if (stderrBytes > maxBufferBytes) {+ fail(+ new Error(+ `${commandLabel(args)} exceeded stderr buffer limit (${maxBufferBytes} bytes).`,+ ),+ );+ }
});
child.on("error", (error) => {
clearTimeout(timeout);
reject(error);
});
child.on("close", (code, signal) => {
+ if (settled) return;+ settled = true;
clearTimeout(timeout);
resolve({ stdout, stderr, code: code ?? null, signal: signal ?? null, timedOut });
});
});
}

As per coding guidelines: Maintain predictable behavior under load and during failures (session restarts, reconnects, partial streams).

Comment threadapps/web/src/components/ChatView.tsx Outdated
Co-authored-by: codex <codex@users.noreply.github.com>
await runGitOrThrow(cwd, args, { allowNonZeroExit });
}

async gitStdout(cwd: string, args: readonly string[], allowNonZeroExit = false): Promise<string> {

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.

🟡 Medium

src/git.ts:359gitStdout discards stderr even on success, so the truncation warning from runGit is lost. Consider checking if output was truncated and either throwing an error or returning a flag so callers know the data is incomplete.

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/git.ts around line 359:
`gitStdout` discards `stderr` even on success, so the truncation warning from `runGit` is lost. Consider checking if output was truncated and either throwing an error or returning a flag so callers know the data is incomplete.

},
);

const rawOutput = (await fs.readFile(outputPath, "utf8")).trim();

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.

🟠 High

src/codexTextGenerator.ts:145fs.readFile at line 145 has no size limit, unlike the maxBufferBytes guard on stdout/stderr in runProcess. Consider adding a file size check (via fs.stat) before reading to prevent OOM if codex produces unexpectedly large output.

- const rawOutput = (await fs.readFile(outputPath, "utf8")).trim();+ const MAX_OUTPUT_BYTES = 8 * 1024 * 1024;+ const stat = await fs.stat(outputPath);+ if (stat.size > MAX_OUTPUT_BYTES) {+ throw new Error(`Codex output exceeded size limit (${MAX_OUTPUT_BYTES} bytes).`);+ }+ const rawOutput = (await fs.readFile(outputPath, "utf8")).trim();

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/codexTextGenerator.ts around line 145:
`fs.readFile` at line 145 has no size limit, unlike the `maxBufferBytes` guard on stdout/stderr in `runProcess`. Consider adding a file size check (via `fs.stat`) before reading to prevent OOM if `codex` produces unexpectedly large output.

- include open PR metadata in git status with graceful `gh` failure handling
- centralize git command execution in `GitCoreService` via `runProcess` with truncation support
- add PTY spawn-helper permission fixes, shell fallback retries, and tests for new behavior
}

async createWorktree(input: GitCreateWorktreeInput): Promise<GitCreateWorktreeResult> {
const sanitizedBranch = input.newBranch.replace(/\//g, "-");

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.

🟡 Medium

src/git.ts:382 Default worktreePath can collide across repos and branches (sanitized names and shared basenames). Suggest deriving a unique, deterministic path (e.g., hash of full repo path + branch) or document if intentional.

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/git.ts around line 382:
Default `worktreePath` can collide across repos and branches (sanitized names and shared basenames). Suggest deriving a unique, deterministic path (e.g., hash of full repo path + branch) or document if intentional.

args.push("-m", trimmedBody);
}
await this.git(cwd, args);
const commitSha = trimStdout(await this.gitStdout(cwd, ["rev-parse", "HEAD"]));

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.

🟢 Low

src/git.ts:256 Race condition: another commit between git commit and git rev-parse HEAD could return the wrong SHA. Consider using git rev-parse HEAD output from the commit command itself, or use git commit --porcelain to get the SHA atomically.

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/git.ts around line 256:
Race condition: another commit between `git commit` and `git rev-parse HEAD` could return the wrong SHA. Consider using `git rev-parse HEAD` output from the commit command itself, or use `git commit --porcelain` to get the SHA atomically.

const worktreeMap = new Map<string, string>();
if (worktreeList.code === 0) {
let currentPath: string | null = null;
for (const line of worktreeList.stdout.split("\n")) {

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.

🟡 Medium

src/git.ts:347 On Windows, split("\n") leaves trailing \r in paths, causing fs.existsSync to fail. Consider using split(/\r?\n/) instead.

Suggested change
for(constlineofworktreeList.stdout.split("\n")){
for(constlineofworktreeList.stdout.split(/\r?\n/)){

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/git.ts around line 347:
On Windows, `split("\n")` leaves trailing `\r` in paths, causing `fs.existsSync` to fail. Consider using `split(/\r?\n/)` instead.

}

async removeWorktree(input: GitRemoveWorktreeInput): Promise<void> {
await executeGit(input.cwd, ["worktree", "remove", input.path], {

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.

🟢 Low

src/git.ts:402 Consider adding -- before input.path to prevent paths starting with - from being interpreted as git options.

Suggested change
awaitexecuteGit(input.cwd,["worktree","remove",input.path],{
awaitexecuteGit(input.cwd,["worktree","remove","--",input.path],{

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/git.ts around line 402:
Consider adding `--` before `input.path` to prevent paths starting with `-` from being interpreted as git options.

- Replace custom inline SVGs with Lucide commit, push, and GitHub icons
- Keep git action behavior unchanged while applying minor style cleanup
Comment threadapps/web/src/components/ChatView.tsx Outdated
juliusmarmingeand others added 3 commits February 12, 2026 12:22
- Add a Git action confirmation modal with live commit/push/PR progress states
- Accept optional `commitMessage` input and skip AI message generation when provided
- Expand server and contracts tests for custom commit message handling
Co-authored-by: codex <codex@users.noreply.github.com>
- Move git menu, modal, and stacked action logic out of `ChatView`
- Render a new `GitActionsControl` wired with `api` and `gitCwd`
- Split Git actions into context-aware Commit, Push, and PR menu items
- Add modal action selection with clearer availability and disabled-state guidance
- Migrate git status and immediate actions to React Query and add a custom GitHub icon
Co-authored-by: codex <codex@users.noreply.github.com>
@coderabbitaicoderabbitaiBot mentioned this pull request Feb 15, 2026
jjalangtry pushed a commit to jjalangtry/t3code that referenced this pull request Mar 16, 2026
dcherrera pushed a commit to dcherrera/t3code that referenced this pull request Jul 7, 2026
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 16, 2026
Makes declarative settings real: a plugin declares a schema and the HOST renders the
form. The plugin ships no form, no RPC, no storage.
The loader now synthesises a settings page from `definition.settings`, so the
declaration is no longer inert (the previous commit shipped the declaration with
nothing consuming it). Generated before register(), so a plugin declaring both gets
its generated page first, then its own; the page id is fixed so a plugin's own
registerSettingsPage cannot shadow it.
PluginSettingsPage wires the concurrency story end to end: the draft carries a
revision, every save sends it back as expectedRevision, and a stale save is rejected
by the server rather than silently clobbering another tab. The server's message is
surfaced verbatim because "does not match the schema" and "changed elsewhere" need
different actions from the user.
The form opens on incompatible stored data and says so, rather than refusing — that
is exactly when the user needs it to repair the values, and the stored data is
preserved until a valid save (Sol spec review MUST pingdotgg#13's recovery half).
This also makes the previously-vacuous test assertable. It now asserts the generated
page appears, and is verified deletion-sensitive: removing the synthesis fails both
new tests. `declared: false` (no schema, or plugin disabled) renders an explanation
rather than an empty form.
Gates: typecheck 0 errors; web 1319 passed; server 1672 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 16, 2026
Sol spec review MUST pingdotgg#13 (detection half). The store recorded a schema fingerprint
from the first commit but nothing ever read it back, so an upgrade that changed a
plugin's schema under already-stored values went undetected — the values were read
as if they still matched, which is exactly the silent misread the fingerprint exists
to prevent.
readDraft now surfaces the fingerprint that produced the stored values, and the
settings RPC compares it against the plugin's CURRENT schema. A mismatch marks the
draft incompatible, which the settings page already renders as "these settings need
attention" with the form open for repair.
The stored data is preserved on mismatch, not discarded: the user may need to read
the old values to reconstruct the new ones, and a plugin that briefly downgrades
should not lose its config. Recovery is a valid save, which rewrites both the values
and the fingerprint.
Tests verified deletion-sensitive: returning a null fingerprint from readDraft fails
both fingerprint tests.
Remaining for MUST pingdotgg#13: hostApi.settings.get should fail InvalidStored on drift
rather than relying on decode alone. The decode already fails for an incompatible
shape in the common case; the fingerprint makes it explicit. Tracked, not claimed.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 16, 2026
Sol spec review MUST pingdotgg#13, plugin-read half. hostApi.settings.get now rejects stored
values whose fingerprint does not match the plugin's current schema, instead of
relying on decode to notice.
Decode is not sufficient and that is the whole point: a widening change (adding an
optional field, relaxing a filter) still decodes cleanly, so the plugin would
silently run on values written for a shape it no longer declares. Comparing the
fingerprint makes the mismatch the fact being checked rather than a side effect of
decoding. The error carries no values — only the plugin id.
KNOWN GAP, stated rather than implied: this path has no test. I wrote one with a
fixture that probes settings.get from a service and records the Exit tag to its data
dir; the probe did not get scheduled within the test's wait, and the "<probe never
ran>" guard I built into it caught that rather than letting it pass vacuously against
whatever I asserted. Rather than ship a test that cannot reliably observe the
behaviour — or one that asserts only "the plugin activated", which would pass with
this check deleted — the tests are removed and the gap recorded.
Store-level fingerprint behaviour IS covered and verified deletion-sensitive
(PluginSettingsStore.test.ts); what is untested is specifically the capability read
path rejecting on drift. Needs a fixture harness that can await a forked service.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 16, 2026
Makes declarative settings real: a plugin declares a schema and the HOST renders the
form. The plugin ships no form, no RPC, no storage.
The loader now synthesises a settings page from `definition.settings`, so the
declaration is no longer inert (the previous commit shipped the declaration with
nothing consuming it). Generated before register(), so a plugin declaring both gets
its generated page first, then its own; the page id is fixed so a plugin's own
registerSettingsPage cannot shadow it.
PluginSettingsPage wires the concurrency story end to end: the draft carries a
revision, every save sends it back as expectedRevision, and a stale save is rejected
by the server rather than silently clobbering another tab. The server's message is
surfaced verbatim because "does not match the schema" and "changed elsewhere" need
different actions from the user.
The form opens on incompatible stored data and says so, rather than refusing — that
is exactly when the user needs it to repair the values, and the stored data is
preserved until a valid save (Sol spec review MUST pingdotgg#13's recovery half).
This also makes the previously-vacuous test assertable. It now asserts the generated
page appears, and is verified deletion-sensitive: removing the synthesis fails both
new tests. `declared: false` (no schema, or plugin disabled) renders an explanation
rather than an empty form.
Gates: typecheck 0 errors; web 1319 passed; server 1672 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 16, 2026
Sol spec review MUST pingdotgg#13 (detection half). The store recorded a schema fingerprint
from the first commit but nothing ever read it back, so an upgrade that changed a
plugin's schema under already-stored values went undetected — the values were read
as if they still matched, which is exactly the silent misread the fingerprint exists
to prevent.
readDraft now surfaces the fingerprint that produced the stored values, and the
settings RPC compares it against the plugin's CURRENT schema. A mismatch marks the
draft incompatible, which the settings page already renders as "these settings need
attention" with the form open for repair.
The stored data is preserved on mismatch, not discarded: the user may need to read
the old values to reconstruct the new ones, and a plugin that briefly downgrades
should not lose its config. Recovery is a valid save, which rewrites both the values
and the fingerprint.
Tests verified deletion-sensitive: returning a null fingerprint from readDraft fails
both fingerprint tests.
Remaining for MUST pingdotgg#13: hostApi.settings.get should fail InvalidStored on drift
rather than relying on decode alone. The decode already fails for an incompatible
shape in the common case; the fingerprint makes it explicit. Tracked, not claimed.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 16, 2026
Sol spec review MUST pingdotgg#13, plugin-read half. hostApi.settings.get now rejects stored
values whose fingerprint does not match the plugin's current schema, instead of
relying on decode to notice.
Decode is not sufficient and that is the whole point: a widening change (adding an
optional field, relaxing a filter) still decodes cleanly, so the plugin would
silently run on values written for a shape it no longer declares. Comparing the
fingerprint makes the mismatch the fact being checked rather than a side effect of
decoding. The error carries no values — only the plugin id.
KNOWN GAP, stated rather than implied: this path has no test. I wrote one with a
fixture that probes settings.get from a service and records the Exit tag to its data
dir; the probe did not get scheduled within the test's wait, and the "<probe never
ran>" guard I built into it caught that rather than letting it pass vacuously against
whatever I asserted. Rather than ship a test that cannot reliably observe the
behaviour — or one that asserts only "the plugin activated", which would pass with
this check deleted — the tests are removed and the gap recorded.
Store-level fingerprint behaviour IS covered and verified deletion-sensitive
(PluginSettingsStore.test.ts); what is untested is specifically the capability read
path rejecting on drift. Needs a fixture harness that can await a forked service.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 17, 2026
Makes declarative settings real: a plugin declares a schema and the HOST renders the
form. The plugin ships no form, no RPC, no storage.
The loader now synthesises a settings page from `definition.settings`, so the
declaration is no longer inert (the previous commit shipped the declaration with
nothing consuming it). Generated before register(), so a plugin declaring both gets
its generated page first, then its own; the page id is fixed so a plugin's own
registerSettingsPage cannot shadow it.
PluginSettingsPage wires the concurrency story end to end: the draft carries a
revision, every save sends it back as expectedRevision, and a stale save is rejected
by the server rather than silently clobbering another tab. The server's message is
surfaced verbatim because "does not match the schema" and "changed elsewhere" need
different actions from the user.
The form opens on incompatible stored data and says so, rather than refusing — that
is exactly when the user needs it to repair the values, and the stored data is
preserved until a valid save (Sol spec review MUST pingdotgg#13's recovery half).
This also makes the previously-vacuous test assertable. It now asserts the generated
page appears, and is verified deletion-sensitive: removing the synthesis fails both
new tests. `declared: false` (no schema, or plugin disabled) renders an explanation
rather than an empty form.
Gates: typecheck 0 errors; web 1319 passed; server 1672 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 17, 2026
Sol spec review MUST pingdotgg#13 (detection half). The store recorded a schema fingerprint
from the first commit but nothing ever read it back, so an upgrade that changed a
plugin's schema under already-stored values went undetected — the values were read
as if they still matched, which is exactly the silent misread the fingerprint exists
to prevent.
readDraft now surfaces the fingerprint that produced the stored values, and the
settings RPC compares it against the plugin's CURRENT schema. A mismatch marks the
draft incompatible, which the settings page already renders as "these settings need
attention" with the form open for repair.
The stored data is preserved on mismatch, not discarded: the user may need to read
the old values to reconstruct the new ones, and a plugin that briefly downgrades
should not lose its config. Recovery is a valid save, which rewrites both the values
and the fingerprint.
Tests verified deletion-sensitive: returning a null fingerprint from readDraft fails
both fingerprint tests.
Remaining for MUST pingdotgg#13: hostApi.settings.get should fail InvalidStored on drift
rather than relying on decode alone. The decode already fails for an incompatible
shape in the common case; the fingerprint makes it explicit. Tracked, not claimed.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 17, 2026
Sol spec review MUST pingdotgg#13, plugin-read half. hostApi.settings.get now rejects stored
values whose fingerprint does not match the plugin's current schema, instead of
relying on decode to notice.
Decode is not sufficient and that is the whole point: a widening change (adding an
optional field, relaxing a filter) still decodes cleanly, so the plugin would
silently run on values written for a shape it no longer declares. Comparing the
fingerprint makes the mismatch the fact being checked rather than a side effect of
decoding. The error carries no values — only the plugin id.
KNOWN GAP, stated rather than implied: this path has no test. I wrote one with a
fixture that probes settings.get from a service and records the Exit tag to its data
dir; the probe did not get scheduled within the test's wait, and the "<probe never
ran>" guard I built into it caught that rather than letting it pass vacuously against
whatever I asserted. Rather than ship a test that cannot reliably observe the
behaviour — or one that asserts only "the plugin activated", which would pass with
this check deleted — the tests are removed and the gap recorded.
Store-level fingerprint behaviour IS covered and verified deletion-sensitive
(PluginSettingsStore.test.ts); what is untested is specifically the capability read
path rejecting on drift. Needs a fixture harness that can await a forked service.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 30, 2026
Makes declarative settings real: a plugin declares a schema and the HOST renders the
form. The plugin ships no form, no RPC, no storage.
The loader now synthesises a settings page from `definition.settings`, so the
declaration is no longer inert (the previous commit shipped the declaration with
nothing consuming it). Generated before register(), so a plugin declaring both gets
its generated page first, then its own; the page id is fixed so a plugin's own
registerSettingsPage cannot shadow it.
PluginSettingsPage wires the concurrency story end to end: the draft carries a
revision, every save sends it back as expectedRevision, and a stale save is rejected
by the server rather than silently clobbering another tab. The server's message is
surfaced verbatim because "does not match the schema" and "changed elsewhere" need
different actions from the user.
The form opens on incompatible stored data and says so, rather than refusing — that
is exactly when the user needs it to repair the values, and the stored data is
preserved until a valid save (Sol spec review MUST pingdotgg#13's recovery half).
This also makes the previously-vacuous test assertable. It now asserts the generated
page appears, and is verified deletion-sensitive: removing the synthesis fails both
new tests. `declared: false` (no schema, or plugin disabled) renders an explanation
rather than an empty form.
Gates: typecheck 0 errors; web 1319 passed; server 1672 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 30, 2026
Sol spec review MUST pingdotgg#13 (detection half). The store recorded a schema fingerprint
from the first commit but nothing ever read it back, so an upgrade that changed a
plugin's schema under already-stored values went undetected — the values were read
as if they still matched, which is exactly the silent misread the fingerprint exists
to prevent.
readDraft now surfaces the fingerprint that produced the stored values, and the
settings RPC compares it against the plugin's CURRENT schema. A mismatch marks the
draft incompatible, which the settings page already renders as "these settings need
attention" with the form open for repair.
The stored data is preserved on mismatch, not discarded: the user may need to read
the old values to reconstruct the new ones, and a plugin that briefly downgrades
should not lose its config. Recovery is a valid save, which rewrites both the values
and the fingerprint.
Tests verified deletion-sensitive: returning a null fingerprint from readDraft fails
both fingerprint tests.
Remaining for MUST pingdotgg#13: hostApi.settings.get should fail InvalidStored on drift
rather than relying on decode alone. The decode already fails for an incompatible
shape in the common case; the fingerprint makes it explicit. Tracked, not claimed.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 30, 2026
Sol spec review MUST pingdotgg#13, plugin-read half. hostApi.settings.get now rejects stored
values whose fingerprint does not match the plugin's current schema, instead of
relying on decode to notice.
Decode is not sufficient and that is the whole point: a widening change (adding an
optional field, relaxing a filter) still decodes cleanly, so the plugin would
silently run on values written for a shape it no longer declares. Comparing the
fingerprint makes the mismatch the fact being checked rather than a side effect of
decoding. The error carries no values — only the plugin id.
KNOWN GAP, stated rather than implied: this path has no test. I wrote one with a
fixture that probes settings.get from a service and records the Exit tag to its data
dir; the probe did not get scheduled within the test's wait, and the "<probe never
ran>" guard I built into it caught that rather than letting it pass vacuously against
whatever I asserted. Rather than ship a test that cannot reliably observe the
behaviour — or one that asserts only "the plugin activated", which would pass with
this check deleted — the tests are removed and the gap recorded.
Store-level fingerprint behaviour IS covered and verified deletion-sensitive
(PluginSettingsStore.test.ts); what is untested is specifically the capability read
path rejecting on drift. Needs a fixture harness that can await a forked service.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 30, 2026
Makes declarative settings real: a plugin declares a schema and the HOST renders the
form. The plugin ships no form, no RPC, no storage.
The loader now synthesises a settings page from `definition.settings`, so the
declaration is no longer inert (the previous commit shipped the declaration with
nothing consuming it). Generated before register(), so a plugin declaring both gets
its generated page first, then its own; the page id is fixed so a plugin's own
registerSettingsPage cannot shadow it.
PluginSettingsPage wires the concurrency story end to end: the draft carries a
revision, every save sends it back as expectedRevision, and a stale save is rejected
by the server rather than silently clobbering another tab. The server's message is
surfaced verbatim because "does not match the schema" and "changed elsewhere" need
different actions from the user.
The form opens on incompatible stored data and says so, rather than refusing — that
is exactly when the user needs it to repair the values, and the stored data is
preserved until a valid save (Sol spec review MUST pingdotgg#13's recovery half).
This also makes the previously-vacuous test assertable. It now asserts the generated
page appears, and is verified deletion-sensitive: removing the synthesis fails both
new tests. `declared: false` (no schema, or plugin disabled) renders an explanation
rather than an empty form.
Gates: typecheck 0 errors; web 1319 passed; server 1672 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 30, 2026
Sol spec review MUST pingdotgg#13 (detection half). The store recorded a schema fingerprint
from the first commit but nothing ever read it back, so an upgrade that changed a
plugin's schema under already-stored values went undetected — the values were read
as if they still matched, which is exactly the silent misread the fingerprint exists
to prevent.
readDraft now surfaces the fingerprint that produced the stored values, and the
settings RPC compares it against the plugin's CURRENT schema. A mismatch marks the
draft incompatible, which the settings page already renders as "these settings need
attention" with the form open for repair.
The stored data is preserved on mismatch, not discarded: the user may need to read
the old values to reconstruct the new ones, and a plugin that briefly downgrades
should not lose its config. Recovery is a valid save, which rewrites both the values
and the fingerprint.
Tests verified deletion-sensitive: returning a null fingerprint from readDraft fails
both fingerprint tests.
Remaining for MUST pingdotgg#13: hostApi.settings.get should fail InvalidStored on drift
rather than relying on decode alone. The decode already fails for an incompatible
shape in the common case; the fingerprint makes it explicit. Tracked, not claimed.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 30, 2026
Sol spec review MUST pingdotgg#13, plugin-read half. hostApi.settings.get now rejects stored
values whose fingerprint does not match the plugin's current schema, instead of
relying on decode to notice.
Decode is not sufficient and that is the whole point: a widening change (adding an
optional field, relaxing a filter) still decodes cleanly, so the plugin would
silently run on values written for a shape it no longer declares. Comparing the
fingerprint makes the mismatch the fact being checked rather than a side effect of
decoding. The error carries no values — only the plugin id.
KNOWN GAP, stated rather than implied: this path has no test. I wrote one with a
fixture that probes settings.get from a service and records the Exit tag to its data
dir; the probe did not get scheduled within the test's wait, and the "<probe never
ran>" guard I built into it caught that rather than letting it pass vacuously against
whatever I asserted. Rather than ship a test that cannot reliably observe the
behaviour — or one that asserts only "the plugin activated", which would pass with
this check deleted — the tests are removed and the gap recorded.
Store-level fingerprint behaviour IS covered and verified deletion-sensitive
(PluginSettingsStore.test.ts); what is untested is specifically the capability read
path rejecting on drift. Needs a fixture harness that can await a forked service.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 31, 2026
Makes declarative settings real: a plugin declares a schema and the HOST renders the
form. The plugin ships no form, no RPC, no storage.
The loader now synthesises a settings page from `definition.settings`, so the
declaration is no longer inert (the previous commit shipped the declaration with
nothing consuming it). Generated before register(), so a plugin declaring both gets
its generated page first, then its own; the page id is fixed so a plugin's own
registerSettingsPage cannot shadow it.
PluginSettingsPage wires the concurrency story end to end: the draft carries a
revision, every save sends it back as expectedRevision, and a stale save is rejected
by the server rather than silently clobbering another tab. The server's message is
surfaced verbatim because "does not match the schema" and "changed elsewhere" need
different actions from the user.
The form opens on incompatible stored data and says so, rather than refusing — that
is exactly when the user needs it to repair the values, and the stored data is
preserved until a valid save (Sol spec review MUST pingdotgg#13's recovery half).
This also makes the previously-vacuous test assertable. It now asserts the generated
page appears, and is verified deletion-sensitive: removing the synthesis fails both
new tests. `declared: false` (no schema, or plugin disabled) renders an explanation
rather than an empty form.
Gates: typecheck 0 errors; web 1319 passed; server 1672 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 31, 2026
Sol spec review MUST pingdotgg#13 (detection half). The store recorded a schema fingerprint
from the first commit but nothing ever read it back, so an upgrade that changed a
plugin's schema under already-stored values went undetected — the values were read
as if they still matched, which is exactly the silent misread the fingerprint exists
to prevent.
readDraft now surfaces the fingerprint that produced the stored values, and the
settings RPC compares it against the plugin's CURRENT schema. A mismatch marks the
draft incompatible, which the settings page already renders as "these settings need
attention" with the form open for repair.
The stored data is preserved on mismatch, not discarded: the user may need to read
the old values to reconstruct the new ones, and a plugin that briefly downgrades
should not lose its config. Recovery is a valid save, which rewrites both the values
and the fingerprint.
Tests verified deletion-sensitive: returning a null fingerprint from readDraft fails
both fingerprint tests.
Remaining for MUST pingdotgg#13: hostApi.settings.get should fail InvalidStored on drift
rather than relying on decode alone. The decode already fails for an incompatible
shape in the common case; the fingerprint makes it explicit. Tracked, not claimed.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 31, 2026
Sol spec review MUST pingdotgg#13, plugin-read half. hostApi.settings.get now rejects stored
values whose fingerprint does not match the plugin's current schema, instead of
relying on decode to notice.
Decode is not sufficient and that is the whole point: a widening change (adding an
optional field, relaxing a filter) still decodes cleanly, so the plugin would
silently run on values written for a shape it no longer declares. Comparing the
fingerprint makes the mismatch the fact being checked rather than a side effect of
decoding. The error carries no values — only the plugin id.
KNOWN GAP, stated rather than implied: this path has no test. I wrote one with a
fixture that probes settings.get from a service and records the Exit tag to its data
dir; the probe did not get scheduled within the test's wait, and the "<probe never
ran>" guard I built into it caught that rather than letting it pass vacuously against
whatever I asserted. Rather than ship a test that cannot reliably observe the
behaviour — or one that asserts only "the plugin activated", which would pass with
this check deleted — the tests are removed and the gap recorded.
Store-level fingerprint behaviour IS covered and verified deletion-sensitive
(PluginSettingsStore.test.ts); what is untested is specifically the capability read
path rejecting on drift. Needs a fixture harness that can await a forked service.
Gates: typecheck 0 errors; server 1675 passed.
aorwall added a commit to aorwall/t3code that referenced this pull request Aug 2, 2026
* docs: spec for preview servers in the T3 Code web app
Approved spec covering the Servers right-panel view, Moatless-owned preview
tabs backed by Redis, and an iframe renderer that lets the existing browser
panel work outside Electron.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat: add the servers.* contract group
Three reads over the servers a thread's environment declares: list them, watch
their status, and follow one's log. Field names are taken verbatim from the
host's own server record so the server side is a rename-free serialization.
Nothing here starts, stops or reconfigures a server; those methods are
specified elsewhere and deliberately out of scope.
The preview module docstring said the preview is desktop-only and that the
desktop renderer mediates. Both stop being true in the same change, so the
docstring now describes the surface by capability rather than by client.
* feat: carry the servers.* group through the client runtime and reference server
Client-side atoms for the three reads, with the log subscription folding
lines into a bounded buffer so a remounted panel keeps what it received
rather than starting over.
The reference server declares no thread servers - it runs threads on the
machine it is on - so it answers the list empty and both subscriptions
silent. A hosted environment answers the same three methods with real data.
Also splits the preview runtime capability in two. One boolean was answering
both "can this runtime show a page" and "can this runtime read the page it
shows", which are the same question only on the desktop app.
* feat: host preview pages in a sandboxed frame on the web
The web app could not show a task's page at all. It can now: one frame per
open tab, mounted at the app root so it survives the panel collapsing, and
positioned by the same surface rect the desktop webview uses.
The frame is driven and never read. Navigation writes to the server and the
frame follows; refresh replaces the element because a cross-origin frame has
no reload and reassigning src would grow the parent's history; back and
forward are absent rather than dead.
Two things a frame cannot report, the panel says from elsewhere. A page that
is not there is explained by what its server says about itself, which is
better than a net error - "installing" is an answer no net error carries. A
page that renders nothing while its server says it is running gets a hint
offered as a hint, because a frame-ancestors refusal and the preview host's
own 401 look identical from outside and neither fires an event.
* feat: add a Servers view to the right panel
One row per server the thread's environment declares, with its status kept
current by the subscription, its log on demand, and an Open that hands the
URL to a browser tab.
Everything in it is a read - a row that says failed offers its log and no
button. Restarting a server is a write and lands elsewhere.
The right panel's persisted state moves to version 8 for the new surface
kind, and its migration now drops surfaces whose kind this build does not
know. That is what makes the version bump safe to downgrade away from: the
rest of the workspace survives and only the unknown tab is lost.
* test: cover the browser preview surface and the servers view
Two products share these schemas and no test process, so the seam is a set
of real Moatless responses checked in here and decoded by the schemas
themselves. When its projection changes the fixture changes with it in one
commit, and the decode test is what fails if the two drift.
The rest covers what the change actually promises: the capability answers
three runtimes, the chrome row omits controls rather than disabling them,
the frame re-keys instead of reassigning src, exactly one browser host
renders, and the panel state survives the version bump while dropping a kind
this build does not know.
* fix: state the environment's absence rather than implying it from a row
The never-provisioned fixture claimed a server is listed as `stopped` with
no URL. It is not. Moatless resolves status config-first from a NotFound
pod, which falls back to `starting` with the ingress URL the port will have
— so the panel showed `starting` forever for an environment that does not
exist, and offered an Open button pointing at a 502.
The fixture now carries what the backend produces, and the panel states the
environment's own status above the list instead of leaving it to be inferred
from rows that cannot say it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: track the fork's own delta, not just what upstream does to it
The policy could answer "who wins this conflict" but not "what did we change",
and the second is the question you are actually asking when git stops on a line
you do not recognise. The path table only ever listed files we expected to fight
over — a much smaller set than the files we changed. The whole thread-servers
group, around forty files, appeared nowhere.
§3 is that missing list, and a fifth hazard in the preamble names the failure it
prevents: a conflict resolved toward upstream because nobody could tell our line
was deliberate. Two consequences: the checklist asks for a row in the same commit
as the change rather than at the next merge, and reading the inventory is now a
step in resolving a conflict rather than something to remember.
Also adds path-policy rows for the highest-risk of those files —
`apps/server/src/ws.ts` and `RpcAuthorization.ts`, the only upstream server files
the fork touches, where the resolution is to take theirs and re-add three
`servers.*` entries.
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
aorwall pushed a commit to aorwall/t3code that referenced this pull request Aug 12, 2026
The 2026-08-08 upstream merge took 89 minutes. Most of that was not conflict
resolution; it was performing checks that existed only as prose in
docs/fork/upstream-merge-inventory.md, one at a time, and rediscovering two
things the doc could have told me before I started.
Five changes, in the order they pay off.
**The inventory is data now.** docs/fork/inventory.json holds the fork-owned
concerns, path policy, fork inventory, tripwires, deliberately deleted upstream
paths, off-repository state and convergence entries. The markdown keeps only
what a check cannot hold — the four re-application deltas, where the
unsupported-method set comes from, and the reasoning behind the path policy
rule — and drops from 784 lines to 243.
**The checks run.** Five dependency-free scripts under the skill:
- `preflight.mjs` — the range, stale entries, the owned-concern sweep, and the
conflict forecast: every file both sides touched, grouped by the verdict that
resolves it, before the merge starts.
- `inventory-check.mjs` — every inventory path read back out of upstream/main.
This is the one that matters: last merge, upstream moved SidebarV2's content
into Sidebar.tsx and Sidebar.tsx's into LegacySidebar.tsx. Git cannot see a
content swap as a rename — it is a delete paired with a modify, which no -M
threshold detects — so it surfaced as a modify/delete conflict mid-merge.
Run against the pre-merge tree, this check names it in about a second.
- `tripwires.mjs`, `unsupported-methods.mjs`, `verify.mjs`.
Running inventory-check against the current tree immediately found three dead
path-policy entries that had been faithfully transcribed forward through
several merges, for directories deleted in pingdotgg#13 and pingdotgg#26.
**Verification is one command, earlier.** `verify.mjs` runs tripwires, the
unsupported-method derivation, format, lint, types and tests — and does not
stop at the first failure, so a formatting nit no longer hides the type errors
behind it. It raises the heap the web suite needs, whose failure mode is
otherwise an exit 137 that reads like a real test failure. The skill now runs
it *before* the documentation steps, because its output is their input.
**Counts come out of prose.** Both snapshots the last merge relied on were
stale. "38 of 87 methods" and "Clerk 4 / pairing 73 / session bootstrap 9" are
replaced by the command that derives them.
**The fork test no longer hard-codes upstream paths.** features.test.ts read
`../components/Sidebar.tsx?raw`; when upstream renamed that file the whole
suite failed to build, at a module path, saying nothing about the gate. It now
reads the guarded files out of inventory.json and reaches them through
import.meta.glob, so a rename fails as a named assertion that says which entry
to re-point — and the test and the merge scripts read the same guards, so they
cannot drift apart.
Verified: fmt, lint, typecheck and 1908 tests pass. `tripwires.mjs` reports one
finding, which is genuine and already tracked — thread-transfer-report.yml went
active on GitHub when the merge branch was pushed and still needs disabling.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
NeilTheFisher pushed a commit to NeilTheFisher/t3code that referenced this pull request Aug 18, 2026
AFLabAI added a commit to AFLabAI/t3code that referenced this pull request Sep 2, 2026
Increase diagnostic visibility by appending complete vp check stdout/stderr to job summary instead of truncating. This exposes all 11 lint errors detected in RUN pingdotgg#13.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Sign up for freeto 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

@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat: Commit, Push and Create PR actions - #13

Merged
juliusmarminge merged 18 commits into
mainfrom
codex/add-github-commit-push-ui
Feb 12, 2026
Merged

feat: Commit, Push and Create PR actions#13
juliusmarminge merged 18 commits into
mainfrom
codex/add-github-commit-push-ui

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Feb 12, 2026

Copy link
Copy Markdown
Member

Open with Devin

Summary by CodeRabbit

  • New Features

    • AI-powered generation for commit messages and PR title/body.
    • New Git core & manager with stacked workflows (commit, commit & push, commit & push & create/open PR), richer status details, and WebSocket/native endpoints to run Git status and actions.
    • UI: Git actions menu with real-time status, action execution, notices and error handling.
  • Chores

    • Robust process runner with buffer and timeout safeguards.
  • Tests

    • Extensive end-to-end and unit tests covering Git flows, PR lifecycle, manager behavior, and contract schemas.

@coderabbitai

coderabbitaiBot commented Feb 12, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds a Git subsystem: process runner with buffer/timeouts, Git core service, GitManager orchestration, Codex-based commit/PR generation, WS API + server wiring, client UI integration, contracts/schemas for stacked git actions, and extensive tests.

Changes

Cohort / File(s)Summary
Text Generation Service
apps/server/src/coreServices.ts, apps/server/src/codexTextGenerator.ts
Defines TextGenerationService types and implements CodexTextGenerator with JSON schemas, validation/parsers, temp-file helpers, runCodexJson orchestration, and methods to generate commit messages and PR content.
Process Execution
apps/server/src/processRunner.ts
Adds runProcess with configurable maxBufferBytes, stdout/stderr accumulation and byte limits, timeout handling (SIGTERM→SIGKILL), spawn/exit normalization, and detailed error types.
Git Core Service
apps/server/src/git.ts
Introduces GitCoreService and helpers: enriched status/statusDetails, prepareCommitContext, commit, pushCurrentBranch, readRangeContext, readConfigValue, runGit helpers, and upstream/default-branch logic.
GitManager Orchestration
apps/server/src/gitManager.ts
Adds GitManager coordinating gitCore, processRunner, Codex text generation, and gh interactions for stacked actions (commit, push, PR), PR discovery/creation, temp-file PR bodies, and error normalization.
Server WS Integration & Tests
apps/server/src/wsServer.ts, apps/server/src/wsServer.test.ts
Wires GitManager into server options, exposes WS methods git.status and git.runStackedAction, and updates tests to inject/validate gitManager delegation and error propagation.
Tests: GitManager & GitCore
apps/server/src/gitManager.test.ts, apps/server/src/git.test.ts
Adds end-to-end tests covering stacked actions, GH CLI scenarios, upstream behavior, detached HEAD, auth errors, and GitCoreService behaviors.
Client Integration
apps/web/src/wsNativeApi.ts, apps/web/src/components/ChatView.tsx
Extends native API with git.status and git.runStackedAction; integrates Git actions menu and UI state into ChatView with status polling, action execution, and notices.
Contracts / IPC / WS Methods & Tests
packages/contracts/src/git.ts, packages/contracts/src/git.test.ts, packages/contracts/src/ipc.ts, packages/contracts/src/ws.ts
Adds Zod schemas/types for git status and stacked actions, updates NativeApi.git signatures, expands WS_METHODS with git.status and git.runStackedAction, and adds schema tests.

Sequence Diagram(s)

sequenceDiagram
participant Client as Client (ChatView)
participant WS as WebSocket Server
participant GM as GitManager
participant GC as GitCoreService
participant Proc as ProcessRunner
participant Codex as Codex Service
participant GH as GitHub CLI
Client->>WS: git.runStackedAction(action, cwd)
WS->>GM: runStackedAction()
rect rgba(100,150,200,0.5)
Note over GM: Commit Step
GM->>GC: prepareCommitContext(cwd)
GC-->>GM: stagedSummary, stagedPatch
GM->>Codex: generateCommitMessage(diff)
Codex-->>GM: subject, body
GM->>Proc: git commit -m "..."
Proc-->>GM: commit result
end
rect rgba(100,200,150,0.5)
Note over GM: Push Step (if requested)
GM->>GC: pushCurrentBranch(cwd, upstream?)
GC-->>GM: push result
end
rect rgba(200,150,100,0.5)
Note over GM: PR Step (if requested)
GM->>GC: readRangeContext(base, head)
GC-->>GM: commitSummary, diffSummary, diffPatch
GM->>Codex: generatePrContent(rangeContext)
Codex-->>GM: title, body
GM->>GH: gh pr list --head branch
GH-->>GM: existing PRs
alt PR exists
GM->>GH: gh pr view PR_NUMBER
GH-->>GM: PR details
else
GM->>GH: gh pr create --title "..." --body file://tmp
GH-->>GM: new PR info
end
end
GM-->>WS: GitRunStackedActionResult
WS-->>Client: result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 2.56% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title 'feat: Commit, Push and Create PR actions' accurately captures the main feature added: three new Git workflow actions integrated into the UI and backend services.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch codex/add-github-commit-push-ui

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps

greptile-appsBot commented Feb 12, 2026

Copy link
Copy Markdown

Greptile Overview

Greptile Summary

This PR implements comprehensive Git stacked actions (commit, push, create PR) with AI-powered text generation for commit messages and PR content. The implementation spans server-side orchestration, robust process execution, WebSocket/IPC integration, and a polished React UI with real-time progress tracking.

Key changes:

  • New GitManager orchestrates stacked workflows (commit → push → PR) with proper error handling and GitHub CLI integration
  • GitCoreService provides low-level Git operations using direct spawn (no shell), with buffer management and timeout safeguards
  • CodexTextGenerator generates commit messages and PR content via Codex CLI with structured JSON output schemas
  • ProcessRunner implements robust subprocess execution with truncation support, graceful termination, and buffer limit enforcement
  • GitActionsControl UI component provides modal-based workflow with step-by-step progress, custom commit messages, and error states
  • Comprehensive test coverage across unit tests (git.test.ts, processRunner.test.ts) and integration tests (gitManager.test.ts)
  • WebSocket and IPC layers extended to expose git.status and git.runStackedAction methods to both web and desktop clients

Architecture aligns with project priorities:

  • Performance: Direct spawn without shell overhead, buffer limits prevent memory issues
  • Reliability: Proper timeout handling, graceful degradation (PR lookup is best-effort), temp file cleanup
  • Predictable behavior: Structured status tracking, deterministic base branch resolution, comprehensive error normalization

Confidence Score: 5/5

  • This PR is safe to merge with high confidence
  • The implementation demonstrates strong engineering discipline: comprehensive test coverage (490 lines in gitManager.test.ts alone), proper error handling with normalized error messages, robust subprocess management with buffer limits and timeouts, dependency injection for testability, and alignment with project priorities (performance, reliability, predictable behavior). The only noted issue is duplicate commit message sanitization which is cosmetic and doesn't affect functionality.
  • No files require special attention

Important Files Changed

FilenameOverview
apps/server/src/git.tsAdds comprehensive Git operations with robust error handling, proper spawn arguments, and buffer management for status, commit, push, and range context operations
apps/server/src/gitManager.tsImplements high-level orchestration for stacked Git workflows (commit, push, PR) with AI text generation, proper dependency injection, and GitHub CLI integration
apps/server/src/codexTextGenerator.tsImplements AI-powered text generation for commit messages and PR content using Codex CLI with structured JSON output, proper sanitization, and temp file cleanup
apps/server/src/processRunner.tsAdds robust process execution with buffer limit enforcement, timeout handling, graceful termination (SIGTERM then SIGKILL), and truncation support
apps/server/src/wsServer.tsIntegrates GitManager into WebSocket server to expose git.status and git.runStackedAction methods via WS protocol
packages/contracts/src/git.tsExtends Git contracts with Zod schemas for stacked action API (status, commit, push, PR) with comprehensive validation rules
apps/web/src/components/GitActionsControl.tsxImplements comprehensive Git actions UI with modal workflow, real-time progress tracking, step-by-step execution, error handling, and PR link opening

Sequence Diagram

sequenceDiagram
participant User
participant GitActionsControl
participant NativeApi
participant GitManager
participant GitCore
participant CodexTextGenerator
participant GitCLI
participant GitHubCLI
User->>GitActionsControl: Click "Commit and create PR"
GitActionsControl->>GitActionsControl: Open modal, set action
User->>GitActionsControl: Confirm action
GitActionsControl->>NativeApi: git.runStackedAction(commit)
NativeApi->>GitManager: runStackedAction(commit)
GitManager->>GitCore: statusDetails(cwd)
GitCore->>GitCLI: git status --porcelain=2
GitCLI-->>GitCore: status output
GitCore-->>GitManager: branch, upstream info
GitManager->>GitCore: prepareCommitContext(cwd)
GitCore->>GitCLI: git add -A
GitCore->>GitCLI: git diff --cached
GitCLI-->>GitCore: staged changes
GitCore-->>GitManager: stagedSummary, stagedPatch
GitManager->>CodexTextGenerator: generateCommitMessage()
CodexTextGenerator->>CodexTextGenerator: Write temp schema file
CodexTextGenerator->>GitCLI: codex exec --output-schema
GitCLI-->>CodexTextGenerator: JSON response
CodexTextGenerator->>CodexTextGenerator: Parse, sanitize, cleanup
CodexTextGenerator-->>GitManager: {subject, body}
GitManager->>GitCore: commit(cwd, subject, body)
GitCore->>GitCLI: git commit -m subject -m body
GitCLI-->>GitCore: success
GitCore-->>GitManager: {commitSha}
GitManager-->>NativeApi: commit result
NativeApi-->>GitActionsControl: Update progress: commit completed
GitActionsControl->>NativeApi: git.runStackedAction(commit_push)
NativeApi->>GitManager: runStackedAction(commit_push)
GitManager->>GitCore: pushCurrentBranch(cwd)
GitCore->>GitCLI: git push -u origin branch
GitCLI-->>GitCore: success
GitCore-->>GitManager: {status: pushed, branch}
GitManager-->>NativeApi: push result
NativeApi-->>GitActionsControl: Update progress: push completed
GitActionsControl->>NativeApi: git.runStackedAction(commit_push_pr)
NativeApi->>GitManager: runStackedAction(commit_push_pr)
GitManager->>GitHubCLI: gh pr list --head branch
GitHubCLI-->>GitManager: [] (no existing PR)
GitManager->>GitCore: readRangeContext(cwd, baseBranch)
GitCore->>GitCLI: git log, git diff
GitCLI-->>GitCore: commit history, diff
GitCore-->>GitManager: rangeContext
GitManager->>CodexTextGenerator: generatePrContent()
CodexTextGenerator->>GitCLI: codex exec --output-schema
GitCLI-->>CodexTextGenerator: JSON response
CodexTextGenerator-->>GitManager: {title, body}
GitManager->>GitHubCLI: gh pr create --title --body-file
GitHubCLI-->>GitManager: PR URL
GitManager->>GitHubCLI: gh pr view --web
GitHubCLI-->>GitManager: success
GitManager-->>NativeApi: pr result
NativeApi-->>GitActionsControl: Update progress: PR created
GitActionsControl->>User: Show completion with PR link
Loading

Last reviewed commit: dd92084

@greptile-appsgreptile-appsBot 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.

14 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment threadapps/server/src/gitManager.ts

@coderabbitaicoderabbitaiBot 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.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@apps/server/src/processRunner.ts`:
- Around line 128-131: Attach an 'error' listener to the child's stdin and use
the write callback to handle possible write errors before calling end: in
processRunner.ts, before calling child.stdin.write(...) register
child.stdin.once("error", err => {/* reject/forward error or cleanup and ensure
promise rejects */}), call child.stdin.write(options.stdin, (err) => { if (err)
{ /* handle/reject/cleanup */ } child.stdin.end(); }); and remove the
unconditional child.stdin.end() so we only end after the write completes; ensure
any error handling forwards the error to the same rejection/cleanup path used by
child.once("error") for the spawned process.
🧹 Nitpick comments (2)
packages/contracts/src/git.test.ts (1)

11-71: LGTM!

The tests provide good coverage for schema validation, including whitespace trimming and nested field parsing.

Consider adding negative test cases to verify that invalid inputs are rejected (e.g., invalid action strings, missing required fields). This would strengthen the contract validation.

,

apps/server/src/codexTextGenerator.ts (1)

84-103: Consider consolidating duplicate sanitization logic.

sanitizeCommitSubject here (lines 84-95) and sanitizeCommitMessage in gitManager.ts (lines 99-110) perform nearly identical operations: extracting the first line, removing trailing periods, and truncating to 72 characters. This duplication could lead to divergent behavior over time.

Consider extracting a shared utility or having gitManager.ts rely on the already-sanitized output from CodexTextGenerator without additional sanitization.

Comment threadapps/server/src/processRunner.ts
Comment threadapps/server/src/gitManager.ts Outdated
Comment threadapps/server/src/processRunner.ts
Comment threadapps/server/src/processRunner.ts
Comment threadapps/server/src/wsServer.ts
Comment threadapps/server/src/gitManager.ts Outdated
Comment threadapps/web/src/components/ChatView.tsx Outdated
Comment threadapps/server/src/codexTextGenerator.ts Outdated
@macroscopeapp

macroscopeappBot commented Feb 12, 2026

Copy link
Copy Markdown
Contributor

Add Commit, Push, and Create PR actions by introducing GitCoreService, GitManager.runStackedAction, and a web GitActionsControl wired through WS and IPC APIs

Implements stacked git workflows across server and web: adds GitCoreService for git operations, CodexTextGenerator for commit/PR text, and GitManager orchestration; exposes git.status and git.runStackedAction over WebSocket; adds desktop bridge shell.openExternal; and introduces a GitActionsControl UI to run commit/push/PR. Terminal spawning gains multi-shell fallback and runProcess provides standardized subprocess handling. See apps/server/src/gitManager.ts, apps/server/src/git.ts, apps/server/src/codexTextGenerator.ts, and apps/web/src/components/GitActionsControl.tsx.

📍Where to Start

Start with the orchestration entrypoint GitManager.runStackedAction in apps/server/src/gitManager.ts, then review GitCoreService in apps/server/src/git.ts and the web client GitActionsControl in apps/web/src/components/GitActionsControl.tsx.


Macroscope summarized dd92084.

Co-authored-by: codex <codex@users.noreply.github.com>
Comment threadapps/server/src/gitManager.ts

@coderabbitaicoderabbitaiBot 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.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@apps/web/src/components/ChatView.tsx`:
- Around line 395-424: When a git status fetch succeeds we need to clear any
previous error so the error banner doesn't persist; inside the load async
function (the one that calls api.git.status with gitCwd) after successfully
calling setGitStatus(nextStatus) also call setGitActionError(null) (guarded by
the same !cancelled check) so successful refreshes remove stale errors; update
the useEffect's load success branch in ChatView.tsx (the load function /
useEffect that references api, gitCwd, setGitStatus, setGitActionError)
accordingly.

Comment threadapps/web/src/components/ChatView.tsx Outdated
Comment threadapps/server/src/git.ts
Comment threadapps/server/src/git.ts Outdated

@coderabbitaicoderabbitaiBot 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.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@apps/server/src/git.test.ts`:
- Around line 460-466: The test invokes git with a single-quoted remote path
which fails on Windows; update the call that runs git(tmp.path, `remote add
origin '${remote.path}'`) to wrap the path using JSON.stringify(remote.path)
(i.e., produce a double-quoted, escaped string) so Windows cmd handles spaces
correctly; modify the test where makeTmpDir()/remote and the git(...) invocation
are used (see git(tmp.path, `remote add origin ...`), initRepoWithCommit,
createGitBranch) to pass the JSON.stringify-wrapped path instead of single
quotes.
In `@apps/server/src/git.ts`:
- Around line 245-251: The current early-return treats a branch as
"skipped_up_to_date" when details.hasUpstream && details.aheadCount === 0 even
if it is behind; update the condition to also require details.behindCount === 0
so we only mark truly up-to-date branches. Change the if that checks
details.hasUpstream and details.aheadCount to: details.hasUpstream &&
details.aheadCount === 0 && details.behindCount === 0 (leaving the returned
object with branch and optional upstreamBranch unchanged).
- Around line 129-143: The timeout check must be unconditional: in
runGitOrThrow, always throw when result.timedOut by calling
normalizeGitExecutionError(args, result) (or similar) before considering
options.allowNonZeroExit; then keep the existing non-zero exit handling for
result.code when options.allowNonZeroExit is false. Update the logic in
runGitOrThrow (referencing runGitOrThrow, RunGitOptions,
options.allowNonZeroExit, result.timedOut, result.code, and
normalizeGitExecutionError) so timeouts are detected and thrown unconditionally
while allowing suppressed non-zero exit codes only when appropriate.
In `@apps/server/src/gitManager.ts`:
- Around line 402-423: Both runGh and runGhStdout currently call this.run("gh",
args, { cwd }) and can hang; add an explicit timeoutMs option to those calls.
Define a clear constant (e.g. GH_CLI_TIMEOUT_MS = 30_000) near the top of the
module and pass it into this.run as { cwd, timeoutMs: GH_CLI_TIMEOUT_MS } in
both runGh and runGhStdout so gh CLI invocations time out predictably. Ensure
the constant is used in both functions and adjust any types if needed to match
ProcessRunOptions.
🧹 Nitpick comments (1)
apps/server/src/gitManager.ts (1)

175-183: If no commit is created, skip push/PR to avoid empty actions.

When runCommitStep returns skipped_no_changes, the current flow still pushes and can attempt PR creation. That can create confusing “no‑op” PRs or unnecessary network calls.

Comment threadapps/server/src/git.test.ts
Comment threadapps/server/src/git.ts
Comment threadapps/server/src/git.ts Outdated
Comment threadapps/server/src/gitManager.ts
@juliusmarmingejuliusmarminge changed the title Add stacked GitHub action workflowGithubFeb 12, 2026
@juliusmarmingejuliusmarminge changed the title Githubfeat: Commit, Push and Create PR actionsFeb 12, 2026
Co-authored-by: codex <codex@users.noreply.github.com>
Comment on lines +123 to +128
const fail = (error: Error): void => {
child.kill("SIGTERM");
finalize(() => {
reject(error);
});
};

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.

🟡 Medium

src/processRunner.ts:123 The fail function clears the timeout that would escalate to SIGKILL, so if the child ignores SIGTERM, the parent hangs forever. Consider adding a force-kill timer in fail similar to the timeout logic (lines 108-110).

- const fail = (error: Error): void => {- child.kill("SIGTERM");- finalize(() => {- reject(error);- });+ const fail = (error: Error): void => {+ child.kill("SIGTERM");+ const killTimer = setTimeout(() => {+ child.kill("SIGKILL");+ }, 1_000);+ finalize(() => {+ clearTimeout(killTimer);+ reject(error);+ });

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/processRunner.ts around lines 123-128:
The `fail` function clears the timeout that would escalate to `SIGKILL`, so if the child ignores `SIGTERM`, the parent hangs forever. Consider adding a force-kill timer in `fail` similar to the timeout logic (lines 108-110).

Comment threadapps/server/src/processRunner.ts Outdated
stream: "stdout" | "stderr",
chunk: Buffer | string,
): Error | null => {
const text = chunk.toString();

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.

🟢 Low

src/processRunner.ts:134 Using chunk.toString() per Buffer can split multi‑byte UTF‑8 and corrupt text; consider StringDecoder to assemble stdout/stderr safely.

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/processRunner.ts around line 134:
Using `chunk.toString()` per `Buffer` can split multi‑byte UTF‑8 and corrupt text; consider `StringDecoder` to assemble `stdout`/`stderr` safely.

@coderabbitaicoderabbitaiBot 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/server/src/git.ts (1)

50-86: ⚠️ Potential issue | 🟠 Major

Add output caps to avoid unbounded git command buffering.
runGit accumulates stdout/stderr without limits; large diffs can spike memory and make behavior unpredictable under load. Consider enforcing a max buffer (similar to processRunner) or streaming to disk with a safe cap.

🔧 Suggested fix (bounded output)
+const DEFAULT_GIT_MAX_BUFFER_BYTES = 8 * 1024 * 1024;+-function runGit(args: readonly string[], cwd: string, timeoutMs = 30_000): Promise<TerminalCommandResult> {+function runGit(+ args: readonly string[],+ cwd: string,+ timeoutMs = 30_000,+ maxBufferBytes = DEFAULT_GIT_MAX_BUFFER_BYTES,+): Promise<TerminalCommandResult> {
return new Promise((resolve, reject) => {
const child = spawn("git", args, {
cwd,
env: process.env,
stdio: ["ignore", "pipe", "pipe"],
});
let stdout = "";
let stderr = "";
let timedOut = false;
+ let stdoutBytes = 0;+ let stderrBytes = 0;+ let settled = false;
const timeout = setTimeout(() => {
timedOut = true;
child.kill("SIGTERM");
setTimeout(() => {
if (!child.killed) child.kill("SIGKILL");
}, 1_000).unref();
}, timeoutMs);
+ const fail = (error: Error) => {+ if (settled) return;+ settled = true;+ clearTimeout(timeout);+ child.kill("SIGTERM");+ reject(error);+ };+
child.stdout?.on("data", (chunk: Buffer) => {
- stdout += chunk.toString();+ const text = chunk.toString();+ stdout += text;+ stdoutBytes += Buffer.byteLength(text);+ if (stdoutBytes > maxBufferBytes) {+ fail(+ new Error(+ `${commandLabel(args)} exceeded stdout buffer limit (${maxBufferBytes} bytes).`,+ ),+ );+ }
});
child.stderr?.on("data", (chunk: Buffer) => {
- stderr += chunk.toString();+ const text = chunk.toString();+ stderr += text;+ stderrBytes += Buffer.byteLength(text);+ if (stderrBytes > maxBufferBytes) {+ fail(+ new Error(+ `${commandLabel(args)} exceeded stderr buffer limit (${maxBufferBytes} bytes).`,+ ),+ );+ }
});
child.on("error", (error) => {
clearTimeout(timeout);
reject(error);
});
child.on("close", (code, signal) => {
+ if (settled) return;+ settled = true;
clearTimeout(timeout);
resolve({ stdout, stderr, code: code ?? null, signal: signal ?? null, timedOut });
});
});
}

As per coding guidelines: Maintain predictable behavior under load and during failures (session restarts, reconnects, partial streams).

Comment threadapps/web/src/components/ChatView.tsx Outdated
Co-authored-by: codex <codex@users.noreply.github.com>
await runGitOrThrow(cwd, args, { allowNonZeroExit });
}

async gitStdout(cwd: string, args: readonly string[], allowNonZeroExit = false): Promise<string> {

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.

🟡 Medium

src/git.ts:359gitStdout discards stderr even on success, so the truncation warning from runGit is lost. Consider checking if output was truncated and either throwing an error or returning a flag so callers know the data is incomplete.

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/git.ts around line 359:
`gitStdout` discards `stderr` even on success, so the truncation warning from `runGit` is lost. Consider checking if output was truncated and either throwing an error or returning a flag so callers know the data is incomplete.

},
);

const rawOutput = (await fs.readFile(outputPath, "utf8")).trim();

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.

🟠 High

src/codexTextGenerator.ts:145fs.readFile at line 145 has no size limit, unlike the maxBufferBytes guard on stdout/stderr in runProcess. Consider adding a file size check (via fs.stat) before reading to prevent OOM if codex produces unexpectedly large output.

- const rawOutput = (await fs.readFile(outputPath, "utf8")).trim();+ const MAX_OUTPUT_BYTES = 8 * 1024 * 1024;+ const stat = await fs.stat(outputPath);+ if (stat.size > MAX_OUTPUT_BYTES) {+ throw new Error(`Codex output exceeded size limit (${MAX_OUTPUT_BYTES} bytes).`);+ }+ const rawOutput = (await fs.readFile(outputPath, "utf8")).trim();

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/codexTextGenerator.ts around line 145:
`fs.readFile` at line 145 has no size limit, unlike the `maxBufferBytes` guard on stdout/stderr in `runProcess`. Consider adding a file size check (via `fs.stat`) before reading to prevent OOM if `codex` produces unexpectedly large output.

- include open PR metadata in git status with graceful `gh` failure handling
- centralize git command execution in `GitCoreService` via `runProcess` with truncation support
- add PTY spawn-helper permission fixes, shell fallback retries, and tests for new behavior
}

async createWorktree(input: GitCreateWorktreeInput): Promise<GitCreateWorktreeResult> {
const sanitizedBranch = input.newBranch.replace(/\//g, "-");

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.

🟡 Medium

src/git.ts:382 Default worktreePath can collide across repos and branches (sanitized names and shared basenames). Suggest deriving a unique, deterministic path (e.g., hash of full repo path + branch) or document if intentional.

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/git.ts around line 382:
Default `worktreePath` can collide across repos and branches (sanitized names and shared basenames). Suggest deriving a unique, deterministic path (e.g., hash of full repo path + branch) or document if intentional.

args.push("-m", trimmedBody);
}
await this.git(cwd, args);
const commitSha = trimStdout(await this.gitStdout(cwd, ["rev-parse", "HEAD"]));

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.

🟢 Low

src/git.ts:256 Race condition: another commit between git commit and git rev-parse HEAD could return the wrong SHA. Consider using git rev-parse HEAD output from the commit command itself, or use git commit --porcelain to get the SHA atomically.

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/git.ts around line 256:
Race condition: another commit between `git commit` and `git rev-parse HEAD` could return the wrong SHA. Consider using `git rev-parse HEAD` output from the commit command itself, or use `git commit --porcelain` to get the SHA atomically.

const worktreeMap = new Map<string, string>();
if (worktreeList.code === 0) {
let currentPath: string | null = null;
for (const line of worktreeList.stdout.split("\n")) {

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.

🟡 Medium

src/git.ts:347 On Windows, split("\n") leaves trailing \r in paths, causing fs.existsSync to fail. Consider using split(/\r?\n/) instead.

Suggested change
for(constlineofworktreeList.stdout.split("\n")){
for(constlineofworktreeList.stdout.split(/\r?\n/)){

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/git.ts around line 347:
On Windows, `split("\n")` leaves trailing `\r` in paths, causing `fs.existsSync` to fail. Consider using `split(/\r?\n/)` instead.

}

async removeWorktree(input: GitRemoveWorktreeInput): Promise<void> {
await executeGit(input.cwd, ["worktree", "remove", input.path], {

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.

🟢 Low

src/git.ts:402 Consider adding -- before input.path to prevent paths starting with - from being interpreted as git options.

Suggested change
awaitexecuteGit(input.cwd,["worktree","remove",input.path],{
awaitexecuteGit(input.cwd,["worktree","remove","--",input.path],{

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/git.ts around line 402:
Consider adding `--` before `input.path` to prevent paths starting with `-` from being interpreted as git options.

- Replace custom inline SVGs with Lucide commit, push, and GitHub icons
- Keep git action behavior unchanged while applying minor style cleanup
Comment threadapps/web/src/components/ChatView.tsx Outdated
juliusmarmingeand others added 3 commits February 12, 2026 12:22
- Add a Git action confirmation modal with live commit/push/PR progress states
- Accept optional `commitMessage` input and skip AI message generation when provided
- Expand server and contracts tests for custom commit message handling
Co-authored-by: codex <codex@users.noreply.github.com>
- Move git menu, modal, and stacked action logic out of `ChatView`
- Render a new `GitActionsControl` wired with `api` and `gitCwd`
- Split Git actions into context-aware Commit, Push, and PR menu items
- Add modal action selection with clearer availability and disabled-state guidance
- Migrate git status and immediate actions to React Query and add a custom GitHub icon
Co-authored-by: codex <codex@users.noreply.github.com>
@coderabbitaicoderabbitaiBot mentioned this pull request Feb 15, 2026
jjalangtry pushed a commit to jjalangtry/t3code that referenced this pull request Mar 16, 2026
dcherrera pushed a commit to dcherrera/t3code that referenced this pull request Jul 7, 2026
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 16, 2026
Makes declarative settings real: a plugin declares a schema and the HOST renders the
form. The plugin ships no form, no RPC, no storage.
The loader now synthesises a settings page from `definition.settings`, so the
declaration is no longer inert (the previous commit shipped the declaration with
nothing consuming it). Generated before register(), so a plugin declaring both gets
its generated page first, then its own; the page id is fixed so a plugin's own
registerSettingsPage cannot shadow it.
PluginSettingsPage wires the concurrency story end to end: the draft carries a
revision, every save sends it back as expectedRevision, and a stale save is rejected
by the server rather than silently clobbering another tab. The server's message is
surfaced verbatim because "does not match the schema" and "changed elsewhere" need
different actions from the user.
The form opens on incompatible stored data and says so, rather than refusing — that
is exactly when the user needs it to repair the values, and the stored data is
preserved until a valid save (Sol spec review MUST pingdotgg#13's recovery half).
This also makes the previously-vacuous test assertable. It now asserts the generated
page appears, and is verified deletion-sensitive: removing the synthesis fails both
new tests. `declared: false` (no schema, or plugin disabled) renders an explanation
rather than an empty form.
Gates: typecheck 0 errors; web 1319 passed; server 1672 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 16, 2026
Sol spec review MUST pingdotgg#13 (detection half). The store recorded a schema fingerprint
from the first commit but nothing ever read it back, so an upgrade that changed a
plugin's schema under already-stored values went undetected — the values were read
as if they still matched, which is exactly the silent misread the fingerprint exists
to prevent.
readDraft now surfaces the fingerprint that produced the stored values, and the
settings RPC compares it against the plugin's CURRENT schema. A mismatch marks the
draft incompatible, which the settings page already renders as "these settings need
attention" with the form open for repair.
The stored data is preserved on mismatch, not discarded: the user may need to read
the old values to reconstruct the new ones, and a plugin that briefly downgrades
should not lose its config. Recovery is a valid save, which rewrites both the values
and the fingerprint.
Tests verified deletion-sensitive: returning a null fingerprint from readDraft fails
both fingerprint tests.
Remaining for MUST pingdotgg#13: hostApi.settings.get should fail InvalidStored on drift
rather than relying on decode alone. The decode already fails for an incompatible
shape in the common case; the fingerprint makes it explicit. Tracked, not claimed.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 16, 2026
Sol spec review MUST pingdotgg#13, plugin-read half. hostApi.settings.get now rejects stored
values whose fingerprint does not match the plugin's current schema, instead of
relying on decode to notice.
Decode is not sufficient and that is the whole point: a widening change (adding an
optional field, relaxing a filter) still decodes cleanly, so the plugin would
silently run on values written for a shape it no longer declares. Comparing the
fingerprint makes the mismatch the fact being checked rather than a side effect of
decoding. The error carries no values — only the plugin id.
KNOWN GAP, stated rather than implied: this path has no test. I wrote one with a
fixture that probes settings.get from a service and records the Exit tag to its data
dir; the probe did not get scheduled within the test's wait, and the "<probe never
ran>" guard I built into it caught that rather than letting it pass vacuously against
whatever I asserted. Rather than ship a test that cannot reliably observe the
behaviour — or one that asserts only "the plugin activated", which would pass with
this check deleted — the tests are removed and the gap recorded.
Store-level fingerprint behaviour IS covered and verified deletion-sensitive
(PluginSettingsStore.test.ts); what is untested is specifically the capability read
path rejecting on drift. Needs a fixture harness that can await a forked service.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 16, 2026
Makes declarative settings real: a plugin declares a schema and the HOST renders the
form. The plugin ships no form, no RPC, no storage.
The loader now synthesises a settings page from `definition.settings`, so the
declaration is no longer inert (the previous commit shipped the declaration with
nothing consuming it). Generated before register(), so a plugin declaring both gets
its generated page first, then its own; the page id is fixed so a plugin's own
registerSettingsPage cannot shadow it.
PluginSettingsPage wires the concurrency story end to end: the draft carries a
revision, every save sends it back as expectedRevision, and a stale save is rejected
by the server rather than silently clobbering another tab. The server's message is
surfaced verbatim because "does not match the schema" and "changed elsewhere" need
different actions from the user.
The form opens on incompatible stored data and says so, rather than refusing — that
is exactly when the user needs it to repair the values, and the stored data is
preserved until a valid save (Sol spec review MUST pingdotgg#13's recovery half).
This also makes the previously-vacuous test assertable. It now asserts the generated
page appears, and is verified deletion-sensitive: removing the synthesis fails both
new tests. `declared: false` (no schema, or plugin disabled) renders an explanation
rather than an empty form.
Gates: typecheck 0 errors; web 1319 passed; server 1672 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 16, 2026
Sol spec review MUST pingdotgg#13 (detection half). The store recorded a schema fingerprint
from the first commit but nothing ever read it back, so an upgrade that changed a
plugin's schema under already-stored values went undetected — the values were read
as if they still matched, which is exactly the silent misread the fingerprint exists
to prevent.
readDraft now surfaces the fingerprint that produced the stored values, and the
settings RPC compares it against the plugin's CURRENT schema. A mismatch marks the
draft incompatible, which the settings page already renders as "these settings need
attention" with the form open for repair.
The stored data is preserved on mismatch, not discarded: the user may need to read
the old values to reconstruct the new ones, and a plugin that briefly downgrades
should not lose its config. Recovery is a valid save, which rewrites both the values
and the fingerprint.
Tests verified deletion-sensitive: returning a null fingerprint from readDraft fails
both fingerprint tests.
Remaining for MUST pingdotgg#13: hostApi.settings.get should fail InvalidStored on drift
rather than relying on decode alone. The decode already fails for an incompatible
shape in the common case; the fingerprint makes it explicit. Tracked, not claimed.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 16, 2026
Sol spec review MUST pingdotgg#13, plugin-read half. hostApi.settings.get now rejects stored
values whose fingerprint does not match the plugin's current schema, instead of
relying on decode to notice.
Decode is not sufficient and that is the whole point: a widening change (adding an
optional field, relaxing a filter) still decodes cleanly, so the plugin would
silently run on values written for a shape it no longer declares. Comparing the
fingerprint makes the mismatch the fact being checked rather than a side effect of
decoding. The error carries no values — only the plugin id.
KNOWN GAP, stated rather than implied: this path has no test. I wrote one with a
fixture that probes settings.get from a service and records the Exit tag to its data
dir; the probe did not get scheduled within the test's wait, and the "<probe never
ran>" guard I built into it caught that rather than letting it pass vacuously against
whatever I asserted. Rather than ship a test that cannot reliably observe the
behaviour — or one that asserts only "the plugin activated", which would pass with
this check deleted — the tests are removed and the gap recorded.
Store-level fingerprint behaviour IS covered and verified deletion-sensitive
(PluginSettingsStore.test.ts); what is untested is specifically the capability read
path rejecting on drift. Needs a fixture harness that can await a forked service.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 17, 2026
Makes declarative settings real: a plugin declares a schema and the HOST renders the
form. The plugin ships no form, no RPC, no storage.
The loader now synthesises a settings page from `definition.settings`, so the
declaration is no longer inert (the previous commit shipped the declaration with
nothing consuming it). Generated before register(), so a plugin declaring both gets
its generated page first, then its own; the page id is fixed so a plugin's own
registerSettingsPage cannot shadow it.
PluginSettingsPage wires the concurrency story end to end: the draft carries a
revision, every save sends it back as expectedRevision, and a stale save is rejected
by the server rather than silently clobbering another tab. The server's message is
surfaced verbatim because "does not match the schema" and "changed elsewhere" need
different actions from the user.
The form opens on incompatible stored data and says so, rather than refusing — that
is exactly when the user needs it to repair the values, and the stored data is
preserved until a valid save (Sol spec review MUST pingdotgg#13's recovery half).
This also makes the previously-vacuous test assertable. It now asserts the generated
page appears, and is verified deletion-sensitive: removing the synthesis fails both
new tests. `declared: false` (no schema, or plugin disabled) renders an explanation
rather than an empty form.
Gates: typecheck 0 errors; web 1319 passed; server 1672 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 17, 2026
Sol spec review MUST pingdotgg#13 (detection half). The store recorded a schema fingerprint
from the first commit but nothing ever read it back, so an upgrade that changed a
plugin's schema under already-stored values went undetected — the values were read
as if they still matched, which is exactly the silent misread the fingerprint exists
to prevent.
readDraft now surfaces the fingerprint that produced the stored values, and the
settings RPC compares it against the plugin's CURRENT schema. A mismatch marks the
draft incompatible, which the settings page already renders as "these settings need
attention" with the form open for repair.
The stored data is preserved on mismatch, not discarded: the user may need to read
the old values to reconstruct the new ones, and a plugin that briefly downgrades
should not lose its config. Recovery is a valid save, which rewrites both the values
and the fingerprint.
Tests verified deletion-sensitive: returning a null fingerprint from readDraft fails
both fingerprint tests.
Remaining for MUST pingdotgg#13: hostApi.settings.get should fail InvalidStored on drift
rather than relying on decode alone. The decode already fails for an incompatible
shape in the common case; the fingerprint makes it explicit. Tracked, not claimed.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 17, 2026
Sol spec review MUST pingdotgg#13, plugin-read half. hostApi.settings.get now rejects stored
values whose fingerprint does not match the plugin's current schema, instead of
relying on decode to notice.
Decode is not sufficient and that is the whole point: a widening change (adding an
optional field, relaxing a filter) still decodes cleanly, so the plugin would
silently run on values written for a shape it no longer declares. Comparing the
fingerprint makes the mismatch the fact being checked rather than a side effect of
decoding. The error carries no values — only the plugin id.
KNOWN GAP, stated rather than implied: this path has no test. I wrote one with a
fixture that probes settings.get from a service and records the Exit tag to its data
dir; the probe did not get scheduled within the test's wait, and the "<probe never
ran>" guard I built into it caught that rather than letting it pass vacuously against
whatever I asserted. Rather than ship a test that cannot reliably observe the
behaviour — or one that asserts only "the plugin activated", which would pass with
this check deleted — the tests are removed and the gap recorded.
Store-level fingerprint behaviour IS covered and verified deletion-sensitive
(PluginSettingsStore.test.ts); what is untested is specifically the capability read
path rejecting on drift. Needs a fixture harness that can await a forked service.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 30, 2026
Makes declarative settings real: a plugin declares a schema and the HOST renders the
form. The plugin ships no form, no RPC, no storage.
The loader now synthesises a settings page from `definition.settings`, so the
declaration is no longer inert (the previous commit shipped the declaration with
nothing consuming it). Generated before register(), so a plugin declaring both gets
its generated page first, then its own; the page id is fixed so a plugin's own
registerSettingsPage cannot shadow it.
PluginSettingsPage wires the concurrency story end to end: the draft carries a
revision, every save sends it back as expectedRevision, and a stale save is rejected
by the server rather than silently clobbering another tab. The server's message is
surfaced verbatim because "does not match the schema" and "changed elsewhere" need
different actions from the user.
The form opens on incompatible stored data and says so, rather than refusing — that
is exactly when the user needs it to repair the values, and the stored data is
preserved until a valid save (Sol spec review MUST pingdotgg#13's recovery half).
This also makes the previously-vacuous test assertable. It now asserts the generated
page appears, and is verified deletion-sensitive: removing the synthesis fails both
new tests. `declared: false` (no schema, or plugin disabled) renders an explanation
rather than an empty form.
Gates: typecheck 0 errors; web 1319 passed; server 1672 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 30, 2026
Sol spec review MUST pingdotgg#13 (detection half). The store recorded a schema fingerprint
from the first commit but nothing ever read it back, so an upgrade that changed a
plugin's schema under already-stored values went undetected — the values were read
as if they still matched, which is exactly the silent misread the fingerprint exists
to prevent.
readDraft now surfaces the fingerprint that produced the stored values, and the
settings RPC compares it against the plugin's CURRENT schema. A mismatch marks the
draft incompatible, which the settings page already renders as "these settings need
attention" with the form open for repair.
The stored data is preserved on mismatch, not discarded: the user may need to read
the old values to reconstruct the new ones, and a plugin that briefly downgrades
should not lose its config. Recovery is a valid save, which rewrites both the values
and the fingerprint.
Tests verified deletion-sensitive: returning a null fingerprint from readDraft fails
both fingerprint tests.
Remaining for MUST pingdotgg#13: hostApi.settings.get should fail InvalidStored on drift
rather than relying on decode alone. The decode already fails for an incompatible
shape in the common case; the fingerprint makes it explicit. Tracked, not claimed.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 30, 2026
Sol spec review MUST pingdotgg#13, plugin-read half. hostApi.settings.get now rejects stored
values whose fingerprint does not match the plugin's current schema, instead of
relying on decode to notice.
Decode is not sufficient and that is the whole point: a widening change (adding an
optional field, relaxing a filter) still decodes cleanly, so the plugin would
silently run on values written for a shape it no longer declares. Comparing the
fingerprint makes the mismatch the fact being checked rather than a side effect of
decoding. The error carries no values — only the plugin id.
KNOWN GAP, stated rather than implied: this path has no test. I wrote one with a
fixture that probes settings.get from a service and records the Exit tag to its data
dir; the probe did not get scheduled within the test's wait, and the "<probe never
ran>" guard I built into it caught that rather than letting it pass vacuously against
whatever I asserted. Rather than ship a test that cannot reliably observe the
behaviour — or one that asserts only "the plugin activated", which would pass with
this check deleted — the tests are removed and the gap recorded.
Store-level fingerprint behaviour IS covered and verified deletion-sensitive
(PluginSettingsStore.test.ts); what is untested is specifically the capability read
path rejecting on drift. Needs a fixture harness that can await a forked service.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 30, 2026
Makes declarative settings real: a plugin declares a schema and the HOST renders the
form. The plugin ships no form, no RPC, no storage.
The loader now synthesises a settings page from `definition.settings`, so the
declaration is no longer inert (the previous commit shipped the declaration with
nothing consuming it). Generated before register(), so a plugin declaring both gets
its generated page first, then its own; the page id is fixed so a plugin's own
registerSettingsPage cannot shadow it.
PluginSettingsPage wires the concurrency story end to end: the draft carries a
revision, every save sends it back as expectedRevision, and a stale save is rejected
by the server rather than silently clobbering another tab. The server's message is
surfaced verbatim because "does not match the schema" and "changed elsewhere" need
different actions from the user.
The form opens on incompatible stored data and says so, rather than refusing — that
is exactly when the user needs it to repair the values, and the stored data is
preserved until a valid save (Sol spec review MUST pingdotgg#13's recovery half).
This also makes the previously-vacuous test assertable. It now asserts the generated
page appears, and is verified deletion-sensitive: removing the synthesis fails both
new tests. `declared: false` (no schema, or plugin disabled) renders an explanation
rather than an empty form.
Gates: typecheck 0 errors; web 1319 passed; server 1672 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 30, 2026
Sol spec review MUST pingdotgg#13 (detection half). The store recorded a schema fingerprint
from the first commit but nothing ever read it back, so an upgrade that changed a
plugin's schema under already-stored values went undetected — the values were read
as if they still matched, which is exactly the silent misread the fingerprint exists
to prevent.
readDraft now surfaces the fingerprint that produced the stored values, and the
settings RPC compares it against the plugin's CURRENT schema. A mismatch marks the
draft incompatible, which the settings page already renders as "these settings need
attention" with the form open for repair.
The stored data is preserved on mismatch, not discarded: the user may need to read
the old values to reconstruct the new ones, and a plugin that briefly downgrades
should not lose its config. Recovery is a valid save, which rewrites both the values
and the fingerprint.
Tests verified deletion-sensitive: returning a null fingerprint from readDraft fails
both fingerprint tests.
Remaining for MUST pingdotgg#13: hostApi.settings.get should fail InvalidStored on drift
rather than relying on decode alone. The decode already fails for an incompatible
shape in the common case; the fingerprint makes it explicit. Tracked, not claimed.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 30, 2026
Sol spec review MUST pingdotgg#13, plugin-read half. hostApi.settings.get now rejects stored
values whose fingerprint does not match the plugin's current schema, instead of
relying on decode to notice.
Decode is not sufficient and that is the whole point: a widening change (adding an
optional field, relaxing a filter) still decodes cleanly, so the plugin would
silently run on values written for a shape it no longer declares. Comparing the
fingerprint makes the mismatch the fact being checked rather than a side effect of
decoding. The error carries no values — only the plugin id.
KNOWN GAP, stated rather than implied: this path has no test. I wrote one with a
fixture that probes settings.get from a service and records the Exit tag to its data
dir; the probe did not get scheduled within the test's wait, and the "<probe never
ran>" guard I built into it caught that rather than letting it pass vacuously against
whatever I asserted. Rather than ship a test that cannot reliably observe the
behaviour — or one that asserts only "the plugin activated", which would pass with
this check deleted — the tests are removed and the gap recorded.
Store-level fingerprint behaviour IS covered and verified deletion-sensitive
(PluginSettingsStore.test.ts); what is untested is specifically the capability read
path rejecting on drift. Needs a fixture harness that can await a forked service.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 31, 2026
Makes declarative settings real: a plugin declares a schema and the HOST renders the
form. The plugin ships no form, no RPC, no storage.
The loader now synthesises a settings page from `definition.settings`, so the
declaration is no longer inert (the previous commit shipped the declaration with
nothing consuming it). Generated before register(), so a plugin declaring both gets
its generated page first, then its own; the page id is fixed so a plugin's own
registerSettingsPage cannot shadow it.
PluginSettingsPage wires the concurrency story end to end: the draft carries a
revision, every save sends it back as expectedRevision, and a stale save is rejected
by the server rather than silently clobbering another tab. The server's message is
surfaced verbatim because "does not match the schema" and "changed elsewhere" need
different actions from the user.
The form opens on incompatible stored data and says so, rather than refusing — that
is exactly when the user needs it to repair the values, and the stored data is
preserved until a valid save (Sol spec review MUST pingdotgg#13's recovery half).
This also makes the previously-vacuous test assertable. It now asserts the generated
page appears, and is verified deletion-sensitive: removing the synthesis fails both
new tests. `declared: false` (no schema, or plugin disabled) renders an explanation
rather than an empty form.
Gates: typecheck 0 errors; web 1319 passed; server 1672 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 31, 2026
Sol spec review MUST pingdotgg#13 (detection half). The store recorded a schema fingerprint
from the first commit but nothing ever read it back, so an upgrade that changed a
plugin's schema under already-stored values went undetected — the values were read
as if they still matched, which is exactly the silent misread the fingerprint exists
to prevent.
readDraft now surfaces the fingerprint that produced the stored values, and the
settings RPC compares it against the plugin's CURRENT schema. A mismatch marks the
draft incompatible, which the settings page already renders as "these settings need
attention" with the form open for repair.
The stored data is preserved on mismatch, not discarded: the user may need to read
the old values to reconstruct the new ones, and a plugin that briefly downgrades
should not lose its config. Recovery is a valid save, which rewrites both the values
and the fingerprint.
Tests verified deletion-sensitive: returning a null fingerprint from readDraft fails
both fingerprint tests.
Remaining for MUST pingdotgg#13: hostApi.settings.get should fail InvalidStored on drift
rather than relying on decode alone. The decode already fails for an incompatible
shape in the common case; the fingerprint makes it explicit. Tracked, not claimed.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 31, 2026
Sol spec review MUST pingdotgg#13, plugin-read half. hostApi.settings.get now rejects stored
values whose fingerprint does not match the plugin's current schema, instead of
relying on decode to notice.
Decode is not sufficient and that is the whole point: a widening change (adding an
optional field, relaxing a filter) still decodes cleanly, so the plugin would
silently run on values written for a shape it no longer declares. Comparing the
fingerprint makes the mismatch the fact being checked rather than a side effect of
decoding. The error carries no values — only the plugin id.
KNOWN GAP, stated rather than implied: this path has no test. I wrote one with a
fixture that probes settings.get from a service and records the Exit tag to its data
dir; the probe did not get scheduled within the test's wait, and the "<probe never
ran>" guard I built into it caught that rather than letting it pass vacuously against
whatever I asserted. Rather than ship a test that cannot reliably observe the
behaviour — or one that asserts only "the plugin activated", which would pass with
this check deleted — the tests are removed and the gap recorded.
Store-level fingerprint behaviour IS covered and verified deletion-sensitive
(PluginSettingsStore.test.ts); what is untested is specifically the capability read
path rejecting on drift. Needs a fixture harness that can await a forked service.
Gates: typecheck 0 errors; server 1675 passed.
aorwall added a commit to aorwall/t3code that referenced this pull request Aug 2, 2026
* docs: spec for preview servers in the T3 Code web app
Approved spec covering the Servers right-panel view, Moatless-owned preview
tabs backed by Redis, and an iframe renderer that lets the existing browser
panel work outside Electron.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat: add the servers.* contract group
Three reads over the servers a thread's environment declares: list them, watch
their status, and follow one's log. Field names are taken verbatim from the
host's own server record so the server side is a rename-free serialization.
Nothing here starts, stops or reconfigures a server; those methods are
specified elsewhere and deliberately out of scope.
The preview module docstring said the preview is desktop-only and that the
desktop renderer mediates. Both stop being true in the same change, so the
docstring now describes the surface by capability rather than by client.
* feat: carry the servers.* group through the client runtime and reference server
Client-side atoms for the three reads, with the log subscription folding
lines into a bounded buffer so a remounted panel keeps what it received
rather than starting over.
The reference server declares no thread servers - it runs threads on the
machine it is on - so it answers the list empty and both subscriptions
silent. A hosted environment answers the same three methods with real data.
Also splits the preview runtime capability in two. One boolean was answering
both "can this runtime show a page" and "can this runtime read the page it
shows", which are the same question only on the desktop app.
* feat: host preview pages in a sandboxed frame on the web
The web app could not show a task's page at all. It can now: one frame per
open tab, mounted at the app root so it survives the panel collapsing, and
positioned by the same surface rect the desktop webview uses.
The frame is driven and never read. Navigation writes to the server and the
frame follows; refresh replaces the element because a cross-origin frame has
no reload and reassigning src would grow the parent's history; back and
forward are absent rather than dead.
Two things a frame cannot report, the panel says from elsewhere. A page that
is not there is explained by what its server says about itself, which is
better than a net error - "installing" is an answer no net error carries. A
page that renders nothing while its server says it is running gets a hint
offered as a hint, because a frame-ancestors refusal and the preview host's
own 401 look identical from outside and neither fires an event.
* feat: add a Servers view to the right panel
One row per server the thread's environment declares, with its status kept
current by the subscription, its log on demand, and an Open that hands the
URL to a browser tab.
Everything in it is a read - a row that says failed offers its log and no
button. Restarting a server is a write and lands elsewhere.
The right panel's persisted state moves to version 8 for the new surface
kind, and its migration now drops surfaces whose kind this build does not
know. That is what makes the version bump safe to downgrade away from: the
rest of the workspace survives and only the unknown tab is lost.
* test: cover the browser preview surface and the servers view
Two products share these schemas and no test process, so the seam is a set
of real Moatless responses checked in here and decoded by the schemas
themselves. When its projection changes the fixture changes with it in one
commit, and the decode test is what fails if the two drift.
The rest covers what the change actually promises: the capability answers
three runtimes, the chrome row omits controls rather than disabling them,
the frame re-keys instead of reassigning src, exactly one browser host
renders, and the panel state survives the version bump while dropping a kind
this build does not know.
* fix: state the environment's absence rather than implying it from a row
The never-provisioned fixture claimed a server is listed as `stopped` with
no URL. It is not. Moatless resolves status config-first from a NotFound
pod, which falls back to `starting` with the ingress URL the port will have
— so the panel showed `starting` forever for an environment that does not
exist, and offered an Open button pointing at a 502.
The fixture now carries what the backend produces, and the panel states the
environment's own status above the list instead of leaving it to be inferred
from rows that cannot say it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: track the fork's own delta, not just what upstream does to it
The policy could answer "who wins this conflict" but not "what did we change",
and the second is the question you are actually asking when git stops on a line
you do not recognise. The path table only ever listed files we expected to fight
over — a much smaller set than the files we changed. The whole thread-servers
group, around forty files, appeared nowhere.
§3 is that missing list, and a fifth hazard in the preamble names the failure it
prevents: a conflict resolved toward upstream because nobody could tell our line
was deliberate. Two consequences: the checklist asks for a row in the same commit
as the change rather than at the next merge, and reading the inventory is now a
step in resolving a conflict rather than something to remember.
Also adds path-policy rows for the highest-risk of those files —
`apps/server/src/ws.ts` and `RpcAuthorization.ts`, the only upstream server files
the fork touches, where the resolution is to take theirs and re-add three
`servers.*` entries.
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
aorwall pushed a commit to aorwall/t3code that referenced this pull request Aug 12, 2026
The 2026-08-08 upstream merge took 89 minutes. Most of that was not conflict
resolution; it was performing checks that existed only as prose in
docs/fork/upstream-merge-inventory.md, one at a time, and rediscovering two
things the doc could have told me before I started.
Five changes, in the order they pay off.
**The inventory is data now.** docs/fork/inventory.json holds the fork-owned
concerns, path policy, fork inventory, tripwires, deliberately deleted upstream
paths, off-repository state and convergence entries. The markdown keeps only
what a check cannot hold — the four re-application deltas, where the
unsupported-method set comes from, and the reasoning behind the path policy
rule — and drops from 784 lines to 243.
**The checks run.** Five dependency-free scripts under the skill:
- `preflight.mjs` — the range, stale entries, the owned-concern sweep, and the
conflict forecast: every file both sides touched, grouped by the verdict that
resolves it, before the merge starts.
- `inventory-check.mjs` — every inventory path read back out of upstream/main.
This is the one that matters: last merge, upstream moved SidebarV2's content
into Sidebar.tsx and Sidebar.tsx's into LegacySidebar.tsx. Git cannot see a
content swap as a rename — it is a delete paired with a modify, which no -M
threshold detects — so it surfaced as a modify/delete conflict mid-merge.
Run against the pre-merge tree, this check names it in about a second.
- `tripwires.mjs`, `unsupported-methods.mjs`, `verify.mjs`.
Running inventory-check against the current tree immediately found three dead
path-policy entries that had been faithfully transcribed forward through
several merges, for directories deleted in pingdotgg#13 and pingdotgg#26.
**Verification is one command, earlier.** `verify.mjs` runs tripwires, the
unsupported-method derivation, format, lint, types and tests — and does not
stop at the first failure, so a formatting nit no longer hides the type errors
behind it. It raises the heap the web suite needs, whose failure mode is
otherwise an exit 137 that reads like a real test failure. The skill now runs
it *before* the documentation steps, because its output is their input.
**Counts come out of prose.** Both snapshots the last merge relied on were
stale. "38 of 87 methods" and "Clerk 4 / pairing 73 / session bootstrap 9" are
replaced by the command that derives them.
**The fork test no longer hard-codes upstream paths.** features.test.ts read
`../components/Sidebar.tsx?raw`; when upstream renamed that file the whole
suite failed to build, at a module path, saying nothing about the gate. It now
reads the guarded files out of inventory.json and reaches them through
import.meta.glob, so a rename fails as a named assertion that says which entry
to re-point — and the test and the merge scripts read the same guards, so they
cannot drift apart.
Verified: fmt, lint, typecheck and 1908 tests pass. `tripwires.mjs` reports one
finding, which is genuine and already tracked — thread-transfer-report.yml went
active on GitHub when the merge branch was pushed and still needs disabling.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
NeilTheFisher pushed a commit to NeilTheFisher/t3code that referenced this pull request Aug 18, 2026
AFLabAI added a commit to AFLabAI/t3code that referenced this pull request Sep 2, 2026
Increase diagnostic visibility by appending complete vp check stdout/stderr to job summary instead of truncating. This exposes all 11 lint errors detected in RUN pingdotgg#13.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Sign up for freeto 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

@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: Commit, Push and Create PR actions - #13

Merged
juliusmarminge merged 18 commits into
mainfrom
codex/add-github-commit-push-ui
Feb 12, 2026
Merged

feat: Commit, Push and Create PR actions#13
juliusmarminge merged 18 commits into
mainfrom
codex/add-github-commit-push-ui

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Feb 12, 2026

Copy link
Copy Markdown
Member

Open with Devin

Summary by CodeRabbit

  • New Features

    • AI-powered generation for commit messages and PR title/body.
    • New Git core & manager with stacked workflows (commit, commit & push, commit & push & create/open PR), richer status details, and WebSocket/native endpoints to run Git status and actions.
    • UI: Git actions menu with real-time status, action execution, notices and error handling.
  • Chores

    • Robust process runner with buffer and timeout safeguards.
  • Tests

    • Extensive end-to-end and unit tests covering Git flows, PR lifecycle, manager behavior, and contract schemas.

@coderabbitai

coderabbitaiBot commented Feb 12, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds a Git subsystem: process runner with buffer/timeouts, Git core service, GitManager orchestration, Codex-based commit/PR generation, WS API + server wiring, client UI integration, contracts/schemas for stacked git actions, and extensive tests.

Changes

Cohort / File(s)Summary
Text Generation Service
apps/server/src/coreServices.ts, apps/server/src/codexTextGenerator.ts
Defines TextGenerationService types and implements CodexTextGenerator with JSON schemas, validation/parsers, temp-file helpers, runCodexJson orchestration, and methods to generate commit messages and PR content.
Process Execution
apps/server/src/processRunner.ts
Adds runProcess with configurable maxBufferBytes, stdout/stderr accumulation and byte limits, timeout handling (SIGTERM→SIGKILL), spawn/exit normalization, and detailed error types.
Git Core Service
apps/server/src/git.ts
Introduces GitCoreService and helpers: enriched status/statusDetails, prepareCommitContext, commit, pushCurrentBranch, readRangeContext, readConfigValue, runGit helpers, and upstream/default-branch logic.
GitManager Orchestration
apps/server/src/gitManager.ts
Adds GitManager coordinating gitCore, processRunner, Codex text generation, and gh interactions for stacked actions (commit, push, PR), PR discovery/creation, temp-file PR bodies, and error normalization.
Server WS Integration & Tests
apps/server/src/wsServer.ts, apps/server/src/wsServer.test.ts
Wires GitManager into server options, exposes WS methods git.status and git.runStackedAction, and updates tests to inject/validate gitManager delegation and error propagation.
Tests: GitManager & GitCore
apps/server/src/gitManager.test.ts, apps/server/src/git.test.ts
Adds end-to-end tests covering stacked actions, GH CLI scenarios, upstream behavior, detached HEAD, auth errors, and GitCoreService behaviors.
Client Integration
apps/web/src/wsNativeApi.ts, apps/web/src/components/ChatView.tsx
Extends native API with git.status and git.runStackedAction; integrates Git actions menu and UI state into ChatView with status polling, action execution, and notices.
Contracts / IPC / WS Methods & Tests
packages/contracts/src/git.ts, packages/contracts/src/git.test.ts, packages/contracts/src/ipc.ts, packages/contracts/src/ws.ts
Adds Zod schemas/types for git status and stacked actions, updates NativeApi.git signatures, expands WS_METHODS with git.status and git.runStackedAction, and adds schema tests.

Sequence Diagram(s)

sequenceDiagram
participant Client as Client (ChatView)
participant WS as WebSocket Server
participant GM as GitManager
participant GC as GitCoreService
participant Proc as ProcessRunner
participant Codex as Codex Service
participant GH as GitHub CLI
Client->>WS: git.runStackedAction(action, cwd)
WS->>GM: runStackedAction()
rect rgba(100,150,200,0.5)
Note over GM: Commit Step
GM->>GC: prepareCommitContext(cwd)
GC-->>GM: stagedSummary, stagedPatch
GM->>Codex: generateCommitMessage(diff)
Codex-->>GM: subject, body
GM->>Proc: git commit -m "..."
Proc-->>GM: commit result
end
rect rgba(100,200,150,0.5)
Note over GM: Push Step (if requested)
GM->>GC: pushCurrentBranch(cwd, upstream?)
GC-->>GM: push result
end
rect rgba(200,150,100,0.5)
Note over GM: PR Step (if requested)
GM->>GC: readRangeContext(base, head)
GC-->>GM: commitSummary, diffSummary, diffPatch
GM->>Codex: generatePrContent(rangeContext)
Codex-->>GM: title, body
GM->>GH: gh pr list --head branch
GH-->>GM: existing PRs
alt PR exists
GM->>GH: gh pr view PR_NUMBER
GH-->>GM: PR details
else
GM->>GH: gh pr create --title "..." --body file://tmp
GH-->>GM: new PR info
end
end
GM-->>WS: GitRunStackedActionResult
WS-->>Client: result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 2.56% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title 'feat: Commit, Push and Create PR actions' accurately captures the main feature added: three new Git workflow actions integrated into the UI and backend services.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch codex/add-github-commit-push-ui

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps

greptile-appsBot commented Feb 12, 2026

Copy link
Copy Markdown

Greptile Overview

Greptile Summary

This PR implements comprehensive Git stacked actions (commit, push, create PR) with AI-powered text generation for commit messages and PR content. The implementation spans server-side orchestration, robust process execution, WebSocket/IPC integration, and a polished React UI with real-time progress tracking.

Key changes:

  • New GitManager orchestrates stacked workflows (commit → push → PR) with proper error handling and GitHub CLI integration
  • GitCoreService provides low-level Git operations using direct spawn (no shell), with buffer management and timeout safeguards
  • CodexTextGenerator generates commit messages and PR content via Codex CLI with structured JSON output schemas
  • ProcessRunner implements robust subprocess execution with truncation support, graceful termination, and buffer limit enforcement
  • GitActionsControl UI component provides modal-based workflow with step-by-step progress, custom commit messages, and error states
  • Comprehensive test coverage across unit tests (git.test.ts, processRunner.test.ts) and integration tests (gitManager.test.ts)
  • WebSocket and IPC layers extended to expose git.status and git.runStackedAction methods to both web and desktop clients

Architecture aligns with project priorities:

  • Performance: Direct spawn without shell overhead, buffer limits prevent memory issues
  • Reliability: Proper timeout handling, graceful degradation (PR lookup is best-effort), temp file cleanup
  • Predictable behavior: Structured status tracking, deterministic base branch resolution, comprehensive error normalization

Confidence Score: 5/5

  • This PR is safe to merge with high confidence
  • The implementation demonstrates strong engineering discipline: comprehensive test coverage (490 lines in gitManager.test.ts alone), proper error handling with normalized error messages, robust subprocess management with buffer limits and timeouts, dependency injection for testability, and alignment with project priorities (performance, reliability, predictable behavior). The only noted issue is duplicate commit message sanitization which is cosmetic and doesn't affect functionality.
  • No files require special attention

Important Files Changed

FilenameOverview
apps/server/src/git.tsAdds comprehensive Git operations with robust error handling, proper spawn arguments, and buffer management for status, commit, push, and range context operations
apps/server/src/gitManager.tsImplements high-level orchestration for stacked Git workflows (commit, push, PR) with AI text generation, proper dependency injection, and GitHub CLI integration
apps/server/src/codexTextGenerator.tsImplements AI-powered text generation for commit messages and PR content using Codex CLI with structured JSON output, proper sanitization, and temp file cleanup
apps/server/src/processRunner.tsAdds robust process execution with buffer limit enforcement, timeout handling, graceful termination (SIGTERM then SIGKILL), and truncation support
apps/server/src/wsServer.tsIntegrates GitManager into WebSocket server to expose git.status and git.runStackedAction methods via WS protocol
packages/contracts/src/git.tsExtends Git contracts with Zod schemas for stacked action API (status, commit, push, PR) with comprehensive validation rules
apps/web/src/components/GitActionsControl.tsxImplements comprehensive Git actions UI with modal workflow, real-time progress tracking, step-by-step execution, error handling, and PR link opening

Sequence Diagram

sequenceDiagram
participant User
participant GitActionsControl
participant NativeApi
participant GitManager
participant GitCore
participant CodexTextGenerator
participant GitCLI
participant GitHubCLI
User->>GitActionsControl: Click "Commit and create PR"
GitActionsControl->>GitActionsControl: Open modal, set action
User->>GitActionsControl: Confirm action
GitActionsControl->>NativeApi: git.runStackedAction(commit)
NativeApi->>GitManager: runStackedAction(commit)
GitManager->>GitCore: statusDetails(cwd)
GitCore->>GitCLI: git status --porcelain=2
GitCLI-->>GitCore: status output
GitCore-->>GitManager: branch, upstream info
GitManager->>GitCore: prepareCommitContext(cwd)
GitCore->>GitCLI: git add -A
GitCore->>GitCLI: git diff --cached
GitCLI-->>GitCore: staged changes
GitCore-->>GitManager: stagedSummary, stagedPatch
GitManager->>CodexTextGenerator: generateCommitMessage()
CodexTextGenerator->>CodexTextGenerator: Write temp schema file
CodexTextGenerator->>GitCLI: codex exec --output-schema
GitCLI-->>CodexTextGenerator: JSON response
CodexTextGenerator->>CodexTextGenerator: Parse, sanitize, cleanup
CodexTextGenerator-->>GitManager: {subject, body}
GitManager->>GitCore: commit(cwd, subject, body)
GitCore->>GitCLI: git commit -m subject -m body
GitCLI-->>GitCore: success
GitCore-->>GitManager: {commitSha}
GitManager-->>NativeApi: commit result
NativeApi-->>GitActionsControl: Update progress: commit completed
GitActionsControl->>NativeApi: git.runStackedAction(commit_push)
NativeApi->>GitManager: runStackedAction(commit_push)
GitManager->>GitCore: pushCurrentBranch(cwd)
GitCore->>GitCLI: git push -u origin branch
GitCLI-->>GitCore: success
GitCore-->>GitManager: {status: pushed, branch}
GitManager-->>NativeApi: push result
NativeApi-->>GitActionsControl: Update progress: push completed
GitActionsControl->>NativeApi: git.runStackedAction(commit_push_pr)
NativeApi->>GitManager: runStackedAction(commit_push_pr)
GitManager->>GitHubCLI: gh pr list --head branch
GitHubCLI-->>GitManager: [] (no existing PR)
GitManager->>GitCore: readRangeContext(cwd, baseBranch)
GitCore->>GitCLI: git log, git diff
GitCLI-->>GitCore: commit history, diff
GitCore-->>GitManager: rangeContext
GitManager->>CodexTextGenerator: generatePrContent()
CodexTextGenerator->>GitCLI: codex exec --output-schema
GitCLI-->>CodexTextGenerator: JSON response
CodexTextGenerator-->>GitManager: {title, body}
GitManager->>GitHubCLI: gh pr create --title --body-file
GitHubCLI-->>GitManager: PR URL
GitManager->>GitHubCLI: gh pr view --web
GitHubCLI-->>GitManager: success
GitManager-->>NativeApi: pr result
NativeApi-->>GitActionsControl: Update progress: PR created
GitActionsControl->>User: Show completion with PR link
Loading

Last reviewed commit: dd92084

@greptile-appsgreptile-appsBot 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.

14 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment threadapps/server/src/gitManager.ts

@coderabbitaicoderabbitaiBot 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.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@apps/server/src/processRunner.ts`:
- Around line 128-131: Attach an 'error' listener to the child's stdin and use
the write callback to handle possible write errors before calling end: in
processRunner.ts, before calling child.stdin.write(...) register
child.stdin.once("error", err => {/* reject/forward error or cleanup and ensure
promise rejects */}), call child.stdin.write(options.stdin, (err) => { if (err)
{ /* handle/reject/cleanup */ } child.stdin.end(); }); and remove the
unconditional child.stdin.end() so we only end after the write completes; ensure
any error handling forwards the error to the same rejection/cleanup path used by
child.once("error") for the spawned process.
🧹 Nitpick comments (2)
packages/contracts/src/git.test.ts (1)

11-71: LGTM!

The tests provide good coverage for schema validation, including whitespace trimming and nested field parsing.

Consider adding negative test cases to verify that invalid inputs are rejected (e.g., invalid action strings, missing required fields). This would strengthen the contract validation.

,

apps/server/src/codexTextGenerator.ts (1)

84-103: Consider consolidating duplicate sanitization logic.

sanitizeCommitSubject here (lines 84-95) and sanitizeCommitMessage in gitManager.ts (lines 99-110) perform nearly identical operations: extracting the first line, removing trailing periods, and truncating to 72 characters. This duplication could lead to divergent behavior over time.

Consider extracting a shared utility or having gitManager.ts rely on the already-sanitized output from CodexTextGenerator without additional sanitization.

Comment threadapps/server/src/processRunner.ts
Comment threadapps/server/src/gitManager.ts Outdated
Comment threadapps/server/src/processRunner.ts
Comment threadapps/server/src/processRunner.ts
Comment threadapps/server/src/wsServer.ts
Comment threadapps/server/src/gitManager.ts Outdated
Comment threadapps/web/src/components/ChatView.tsx Outdated
Comment threadapps/server/src/codexTextGenerator.ts Outdated
@macroscopeapp

macroscopeappBot commented Feb 12, 2026

Copy link
Copy Markdown
Contributor

Add Commit, Push, and Create PR actions by introducing GitCoreService, GitManager.runStackedAction, and a web GitActionsControl wired through WS and IPC APIs

Implements stacked git workflows across server and web: adds GitCoreService for git operations, CodexTextGenerator for commit/PR text, and GitManager orchestration; exposes git.status and git.runStackedAction over WebSocket; adds desktop bridge shell.openExternal; and introduces a GitActionsControl UI to run commit/push/PR. Terminal spawning gains multi-shell fallback and runProcess provides standardized subprocess handling. See apps/server/src/gitManager.ts, apps/server/src/git.ts, apps/server/src/codexTextGenerator.ts, and apps/web/src/components/GitActionsControl.tsx.

📍Where to Start

Start with the orchestration entrypoint GitManager.runStackedAction in apps/server/src/gitManager.ts, then review GitCoreService in apps/server/src/git.ts and the web client GitActionsControl in apps/web/src/components/GitActionsControl.tsx.


Macroscope summarized dd92084.

Co-authored-by: codex <codex@users.noreply.github.com>
Comment threadapps/server/src/gitManager.ts

@coderabbitaicoderabbitaiBot 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.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@apps/web/src/components/ChatView.tsx`:
- Around line 395-424: When a git status fetch succeeds we need to clear any
previous error so the error banner doesn't persist; inside the load async
function (the one that calls api.git.status with gitCwd) after successfully
calling setGitStatus(nextStatus) also call setGitActionError(null) (guarded by
the same !cancelled check) so successful refreshes remove stale errors; update
the useEffect's load success branch in ChatView.tsx (the load function /
useEffect that references api, gitCwd, setGitStatus, setGitActionError)
accordingly.

Comment threadapps/web/src/components/ChatView.tsx Outdated
Comment threadapps/server/src/git.ts
Comment threadapps/server/src/git.ts Outdated

@coderabbitaicoderabbitaiBot 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.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@apps/server/src/git.test.ts`:
- Around line 460-466: The test invokes git with a single-quoted remote path
which fails on Windows; update the call that runs git(tmp.path, `remote add
origin '${remote.path}'`) to wrap the path using JSON.stringify(remote.path)
(i.e., produce a double-quoted, escaped string) so Windows cmd handles spaces
correctly; modify the test where makeTmpDir()/remote and the git(...) invocation
are used (see git(tmp.path, `remote add origin ...`), initRepoWithCommit,
createGitBranch) to pass the JSON.stringify-wrapped path instead of single
quotes.
In `@apps/server/src/git.ts`:
- Around line 245-251: The current early-return treats a branch as
"skipped_up_to_date" when details.hasUpstream && details.aheadCount === 0 even
if it is behind; update the condition to also require details.behindCount === 0
so we only mark truly up-to-date branches. Change the if that checks
details.hasUpstream and details.aheadCount to: details.hasUpstream &&
details.aheadCount === 0 && details.behindCount === 0 (leaving the returned
object with branch and optional upstreamBranch unchanged).
- Around line 129-143: The timeout check must be unconditional: in
runGitOrThrow, always throw when result.timedOut by calling
normalizeGitExecutionError(args, result) (or similar) before considering
options.allowNonZeroExit; then keep the existing non-zero exit handling for
result.code when options.allowNonZeroExit is false. Update the logic in
runGitOrThrow (referencing runGitOrThrow, RunGitOptions,
options.allowNonZeroExit, result.timedOut, result.code, and
normalizeGitExecutionError) so timeouts are detected and thrown unconditionally
while allowing suppressed non-zero exit codes only when appropriate.
In `@apps/server/src/gitManager.ts`:
- Around line 402-423: Both runGh and runGhStdout currently call this.run("gh",
args, { cwd }) and can hang; add an explicit timeoutMs option to those calls.
Define a clear constant (e.g. GH_CLI_TIMEOUT_MS = 30_000) near the top of the
module and pass it into this.run as { cwd, timeoutMs: GH_CLI_TIMEOUT_MS } in
both runGh and runGhStdout so gh CLI invocations time out predictably. Ensure
the constant is used in both functions and adjust any types if needed to match
ProcessRunOptions.
🧹 Nitpick comments (1)
apps/server/src/gitManager.ts (1)

175-183: If no commit is created, skip push/PR to avoid empty actions.

When runCommitStep returns skipped_no_changes, the current flow still pushes and can attempt PR creation. That can create confusing “no‑op” PRs or unnecessary network calls.

Comment threadapps/server/src/git.test.ts
Comment threadapps/server/src/git.ts
Comment threadapps/server/src/git.ts Outdated
Comment threadapps/server/src/gitManager.ts
@juliusmarmingejuliusmarminge changed the title Add stacked GitHub action workflowGithubFeb 12, 2026
@juliusmarmingejuliusmarminge changed the title Githubfeat: Commit, Push and Create PR actionsFeb 12, 2026
Co-authored-by: codex <codex@users.noreply.github.com>
Comment on lines +123 to +128
const fail = (error: Error): void => {
child.kill("SIGTERM");
finalize(() => {
reject(error);
});
};

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.

🟡 Medium

src/processRunner.ts:123 The fail function clears the timeout that would escalate to SIGKILL, so if the child ignores SIGTERM, the parent hangs forever. Consider adding a force-kill timer in fail similar to the timeout logic (lines 108-110).

- const fail = (error: Error): void => {- child.kill("SIGTERM");- finalize(() => {- reject(error);- });+ const fail = (error: Error): void => {+ child.kill("SIGTERM");+ const killTimer = setTimeout(() => {+ child.kill("SIGKILL");+ }, 1_000);+ finalize(() => {+ clearTimeout(killTimer);+ reject(error);+ });

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/processRunner.ts around lines 123-128:
The `fail` function clears the timeout that would escalate to `SIGKILL`, so if the child ignores `SIGTERM`, the parent hangs forever. Consider adding a force-kill timer in `fail` similar to the timeout logic (lines 108-110).

Comment threadapps/server/src/processRunner.ts Outdated
stream: "stdout" | "stderr",
chunk: Buffer | string,
): Error | null => {
const text = chunk.toString();

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.

🟢 Low

src/processRunner.ts:134 Using chunk.toString() per Buffer can split multi‑byte UTF‑8 and corrupt text; consider StringDecoder to assemble stdout/stderr safely.

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/processRunner.ts around line 134:
Using `chunk.toString()` per `Buffer` can split multi‑byte UTF‑8 and corrupt text; consider `StringDecoder` to assemble `stdout`/`stderr` safely.

@coderabbitaicoderabbitaiBot 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/server/src/git.ts (1)

50-86: ⚠️ Potential issue | 🟠 Major

Add output caps to avoid unbounded git command buffering.
runGit accumulates stdout/stderr without limits; large diffs can spike memory and make behavior unpredictable under load. Consider enforcing a max buffer (similar to processRunner) or streaming to disk with a safe cap.

🔧 Suggested fix (bounded output)
+const DEFAULT_GIT_MAX_BUFFER_BYTES = 8 * 1024 * 1024;+-function runGit(args: readonly string[], cwd: string, timeoutMs = 30_000): Promise<TerminalCommandResult> {+function runGit(+ args: readonly string[],+ cwd: string,+ timeoutMs = 30_000,+ maxBufferBytes = DEFAULT_GIT_MAX_BUFFER_BYTES,+): Promise<TerminalCommandResult> {
return new Promise((resolve, reject) => {
const child = spawn("git", args, {
cwd,
env: process.env,
stdio: ["ignore", "pipe", "pipe"],
});
let stdout = "";
let stderr = "";
let timedOut = false;
+ let stdoutBytes = 0;+ let stderrBytes = 0;+ let settled = false;
const timeout = setTimeout(() => {
timedOut = true;
child.kill("SIGTERM");
setTimeout(() => {
if (!child.killed) child.kill("SIGKILL");
}, 1_000).unref();
}, timeoutMs);
+ const fail = (error: Error) => {+ if (settled) return;+ settled = true;+ clearTimeout(timeout);+ child.kill("SIGTERM");+ reject(error);+ };+
child.stdout?.on("data", (chunk: Buffer) => {
- stdout += chunk.toString();+ const text = chunk.toString();+ stdout += text;+ stdoutBytes += Buffer.byteLength(text);+ if (stdoutBytes > maxBufferBytes) {+ fail(+ new Error(+ `${commandLabel(args)} exceeded stdout buffer limit (${maxBufferBytes} bytes).`,+ ),+ );+ }
});
child.stderr?.on("data", (chunk: Buffer) => {
- stderr += chunk.toString();+ const text = chunk.toString();+ stderr += text;+ stderrBytes += Buffer.byteLength(text);+ if (stderrBytes > maxBufferBytes) {+ fail(+ new Error(+ `${commandLabel(args)} exceeded stderr buffer limit (${maxBufferBytes} bytes).`,+ ),+ );+ }
});
child.on("error", (error) => {
clearTimeout(timeout);
reject(error);
});
child.on("close", (code, signal) => {
+ if (settled) return;+ settled = true;
clearTimeout(timeout);
resolve({ stdout, stderr, code: code ?? null, signal: signal ?? null, timedOut });
});
});
}

As per coding guidelines: Maintain predictable behavior under load and during failures (session restarts, reconnects, partial streams).

Comment threadapps/web/src/components/ChatView.tsx Outdated
Co-authored-by: codex <codex@users.noreply.github.com>
await runGitOrThrow(cwd, args, { allowNonZeroExit });
}

async gitStdout(cwd: string, args: readonly string[], allowNonZeroExit = false): Promise<string> {

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.

🟡 Medium

src/git.ts:359gitStdout discards stderr even on success, so the truncation warning from runGit is lost. Consider checking if output was truncated and either throwing an error or returning a flag so callers know the data is incomplete.

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/git.ts around line 359:
`gitStdout` discards `stderr` even on success, so the truncation warning from `runGit` is lost. Consider checking if output was truncated and either throwing an error or returning a flag so callers know the data is incomplete.

},
);

const rawOutput = (await fs.readFile(outputPath, "utf8")).trim();

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.

🟠 High

src/codexTextGenerator.ts:145fs.readFile at line 145 has no size limit, unlike the maxBufferBytes guard on stdout/stderr in runProcess. Consider adding a file size check (via fs.stat) before reading to prevent OOM if codex produces unexpectedly large output.

- const rawOutput = (await fs.readFile(outputPath, "utf8")).trim();+ const MAX_OUTPUT_BYTES = 8 * 1024 * 1024;+ const stat = await fs.stat(outputPath);+ if (stat.size > MAX_OUTPUT_BYTES) {+ throw new Error(`Codex output exceeded size limit (${MAX_OUTPUT_BYTES} bytes).`);+ }+ const rawOutput = (await fs.readFile(outputPath, "utf8")).trim();

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/codexTextGenerator.ts around line 145:
`fs.readFile` at line 145 has no size limit, unlike the `maxBufferBytes` guard on stdout/stderr in `runProcess`. Consider adding a file size check (via `fs.stat`) before reading to prevent OOM if `codex` produces unexpectedly large output.

- include open PR metadata in git status with graceful `gh` failure handling
- centralize git command execution in `GitCoreService` via `runProcess` with truncation support
- add PTY spawn-helper permission fixes, shell fallback retries, and tests for new behavior
}

async createWorktree(input: GitCreateWorktreeInput): Promise<GitCreateWorktreeResult> {
const sanitizedBranch = input.newBranch.replace(/\//g, "-");

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.

🟡 Medium

src/git.ts:382 Default worktreePath can collide across repos and branches (sanitized names and shared basenames). Suggest deriving a unique, deterministic path (e.g., hash of full repo path + branch) or document if intentional.

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/git.ts around line 382:
Default `worktreePath` can collide across repos and branches (sanitized names and shared basenames). Suggest deriving a unique, deterministic path (e.g., hash of full repo path + branch) or document if intentional.

args.push("-m", trimmedBody);
}
await this.git(cwd, args);
const commitSha = trimStdout(await this.gitStdout(cwd, ["rev-parse", "HEAD"]));

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.

🟢 Low

src/git.ts:256 Race condition: another commit between git commit and git rev-parse HEAD could return the wrong SHA. Consider using git rev-parse HEAD output from the commit command itself, or use git commit --porcelain to get the SHA atomically.

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/git.ts around line 256:
Race condition: another commit between `git commit` and `git rev-parse HEAD` could return the wrong SHA. Consider using `git rev-parse HEAD` output from the commit command itself, or use `git commit --porcelain` to get the SHA atomically.

const worktreeMap = new Map<string, string>();
if (worktreeList.code === 0) {
let currentPath: string | null = null;
for (const line of worktreeList.stdout.split("\n")) {

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.

🟡 Medium

src/git.ts:347 On Windows, split("\n") leaves trailing \r in paths, causing fs.existsSync to fail. Consider using split(/\r?\n/) instead.

Suggested change
for(constlineofworktreeList.stdout.split("\n")){
for(constlineofworktreeList.stdout.split(/\r?\n/)){

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/git.ts around line 347:
On Windows, `split("\n")` leaves trailing `\r` in paths, causing `fs.existsSync` to fail. Consider using `split(/\r?\n/)` instead.

}

async removeWorktree(input: GitRemoveWorktreeInput): Promise<void> {
await executeGit(input.cwd, ["worktree", "remove", input.path], {

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.

🟢 Low

src/git.ts:402 Consider adding -- before input.path to prevent paths starting with - from being interpreted as git options.

Suggested change
awaitexecuteGit(input.cwd,["worktree","remove",input.path],{
awaitexecuteGit(input.cwd,["worktree","remove","--",input.path],{

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/git.ts around line 402:
Consider adding `--` before `input.path` to prevent paths starting with `-` from being interpreted as git options.

- Replace custom inline SVGs with Lucide commit, push, and GitHub icons
- Keep git action behavior unchanged while applying minor style cleanup
Comment threadapps/web/src/components/ChatView.tsx Outdated
juliusmarmingeand others added 3 commits February 12, 2026 12:22
- Add a Git action confirmation modal with live commit/push/PR progress states
- Accept optional `commitMessage` input and skip AI message generation when provided
- Expand server and contracts tests for custom commit message handling
Co-authored-by: codex <codex@users.noreply.github.com>
- Move git menu, modal, and stacked action logic out of `ChatView`
- Render a new `GitActionsControl` wired with `api` and `gitCwd`
- Split Git actions into context-aware Commit, Push, and PR menu items
- Add modal action selection with clearer availability and disabled-state guidance
- Migrate git status and immediate actions to React Query and add a custom GitHub icon
Co-authored-by: codex <codex@users.noreply.github.com>
@coderabbitaicoderabbitaiBot mentioned this pull request Feb 15, 2026
jjalangtry pushed a commit to jjalangtry/t3code that referenced this pull request Mar 16, 2026
dcherrera pushed a commit to dcherrera/t3code that referenced this pull request Jul 7, 2026
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 16, 2026
Makes declarative settings real: a plugin declares a schema and the HOST renders the
form. The plugin ships no form, no RPC, no storage.
The loader now synthesises a settings page from `definition.settings`, so the
declaration is no longer inert (the previous commit shipped the declaration with
nothing consuming it). Generated before register(), so a plugin declaring both gets
its generated page first, then its own; the page id is fixed so a plugin's own
registerSettingsPage cannot shadow it.
PluginSettingsPage wires the concurrency story end to end: the draft carries a
revision, every save sends it back as expectedRevision, and a stale save is rejected
by the server rather than silently clobbering another tab. The server's message is
surfaced verbatim because "does not match the schema" and "changed elsewhere" need
different actions from the user.
The form opens on incompatible stored data and says so, rather than refusing — that
is exactly when the user needs it to repair the values, and the stored data is
preserved until a valid save (Sol spec review MUST pingdotgg#13's recovery half).
This also makes the previously-vacuous test assertable. It now asserts the generated
page appears, and is verified deletion-sensitive: removing the synthesis fails both
new tests. `declared: false` (no schema, or plugin disabled) renders an explanation
rather than an empty form.
Gates: typecheck 0 errors; web 1319 passed; server 1672 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 16, 2026
Sol spec review MUST pingdotgg#13 (detection half). The store recorded a schema fingerprint
from the first commit but nothing ever read it back, so an upgrade that changed a
plugin's schema under already-stored values went undetected — the values were read
as if they still matched, which is exactly the silent misread the fingerprint exists
to prevent.
readDraft now surfaces the fingerprint that produced the stored values, and the
settings RPC compares it against the plugin's CURRENT schema. A mismatch marks the
draft incompatible, which the settings page already renders as "these settings need
attention" with the form open for repair.
The stored data is preserved on mismatch, not discarded: the user may need to read
the old values to reconstruct the new ones, and a plugin that briefly downgrades
should not lose its config. Recovery is a valid save, which rewrites both the values
and the fingerprint.
Tests verified deletion-sensitive: returning a null fingerprint from readDraft fails
both fingerprint tests.
Remaining for MUST pingdotgg#13: hostApi.settings.get should fail InvalidStored on drift
rather than relying on decode alone. The decode already fails for an incompatible
shape in the common case; the fingerprint makes it explicit. Tracked, not claimed.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 16, 2026
Sol spec review MUST pingdotgg#13, plugin-read half. hostApi.settings.get now rejects stored
values whose fingerprint does not match the plugin's current schema, instead of
relying on decode to notice.
Decode is not sufficient and that is the whole point: a widening change (adding an
optional field, relaxing a filter) still decodes cleanly, so the plugin would
silently run on values written for a shape it no longer declares. Comparing the
fingerprint makes the mismatch the fact being checked rather than a side effect of
decoding. The error carries no values — only the plugin id.
KNOWN GAP, stated rather than implied: this path has no test. I wrote one with a
fixture that probes settings.get from a service and records the Exit tag to its data
dir; the probe did not get scheduled within the test's wait, and the "<probe never
ran>" guard I built into it caught that rather than letting it pass vacuously against
whatever I asserted. Rather than ship a test that cannot reliably observe the
behaviour — or one that asserts only "the plugin activated", which would pass with
this check deleted — the tests are removed and the gap recorded.
Store-level fingerprint behaviour IS covered and verified deletion-sensitive
(PluginSettingsStore.test.ts); what is untested is specifically the capability read
path rejecting on drift. Needs a fixture harness that can await a forked service.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 16, 2026
Makes declarative settings real: a plugin declares a schema and the HOST renders the
form. The plugin ships no form, no RPC, no storage.
The loader now synthesises a settings page from `definition.settings`, so the
declaration is no longer inert (the previous commit shipped the declaration with
nothing consuming it). Generated before register(), so a plugin declaring both gets
its generated page first, then its own; the page id is fixed so a plugin's own
registerSettingsPage cannot shadow it.
PluginSettingsPage wires the concurrency story end to end: the draft carries a
revision, every save sends it back as expectedRevision, and a stale save is rejected
by the server rather than silently clobbering another tab. The server's message is
surfaced verbatim because "does not match the schema" and "changed elsewhere" need
different actions from the user.
The form opens on incompatible stored data and says so, rather than refusing — that
is exactly when the user needs it to repair the values, and the stored data is
preserved until a valid save (Sol spec review MUST pingdotgg#13's recovery half).
This also makes the previously-vacuous test assertable. It now asserts the generated
page appears, and is verified deletion-sensitive: removing the synthesis fails both
new tests. `declared: false` (no schema, or plugin disabled) renders an explanation
rather than an empty form.
Gates: typecheck 0 errors; web 1319 passed; server 1672 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 16, 2026
Sol spec review MUST pingdotgg#13 (detection half). The store recorded a schema fingerprint
from the first commit but nothing ever read it back, so an upgrade that changed a
plugin's schema under already-stored values went undetected — the values were read
as if they still matched, which is exactly the silent misread the fingerprint exists
to prevent.
readDraft now surfaces the fingerprint that produced the stored values, and the
settings RPC compares it against the plugin's CURRENT schema. A mismatch marks the
draft incompatible, which the settings page already renders as "these settings need
attention" with the form open for repair.
The stored data is preserved on mismatch, not discarded: the user may need to read
the old values to reconstruct the new ones, and a plugin that briefly downgrades
should not lose its config. Recovery is a valid save, which rewrites both the values
and the fingerprint.
Tests verified deletion-sensitive: returning a null fingerprint from readDraft fails
both fingerprint tests.
Remaining for MUST pingdotgg#13: hostApi.settings.get should fail InvalidStored on drift
rather than relying on decode alone. The decode already fails for an incompatible
shape in the common case; the fingerprint makes it explicit. Tracked, not claimed.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 16, 2026
Sol spec review MUST pingdotgg#13, plugin-read half. hostApi.settings.get now rejects stored
values whose fingerprint does not match the plugin's current schema, instead of
relying on decode to notice.
Decode is not sufficient and that is the whole point: a widening change (adding an
optional field, relaxing a filter) still decodes cleanly, so the plugin would
silently run on values written for a shape it no longer declares. Comparing the
fingerprint makes the mismatch the fact being checked rather than a side effect of
decoding. The error carries no values — only the plugin id.
KNOWN GAP, stated rather than implied: this path has no test. I wrote one with a
fixture that probes settings.get from a service and records the Exit tag to its data
dir; the probe did not get scheduled within the test's wait, and the "<probe never
ran>" guard I built into it caught that rather than letting it pass vacuously against
whatever I asserted. Rather than ship a test that cannot reliably observe the
behaviour — or one that asserts only "the plugin activated", which would pass with
this check deleted — the tests are removed and the gap recorded.
Store-level fingerprint behaviour IS covered and verified deletion-sensitive
(PluginSettingsStore.test.ts); what is untested is specifically the capability read
path rejecting on drift. Needs a fixture harness that can await a forked service.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 17, 2026
Makes declarative settings real: a plugin declares a schema and the HOST renders the
form. The plugin ships no form, no RPC, no storage.
The loader now synthesises a settings page from `definition.settings`, so the
declaration is no longer inert (the previous commit shipped the declaration with
nothing consuming it). Generated before register(), so a plugin declaring both gets
its generated page first, then its own; the page id is fixed so a plugin's own
registerSettingsPage cannot shadow it.
PluginSettingsPage wires the concurrency story end to end: the draft carries a
revision, every save sends it back as expectedRevision, and a stale save is rejected
by the server rather than silently clobbering another tab. The server's message is
surfaced verbatim because "does not match the schema" and "changed elsewhere" need
different actions from the user.
The form opens on incompatible stored data and says so, rather than refusing — that
is exactly when the user needs it to repair the values, and the stored data is
preserved until a valid save (Sol spec review MUST pingdotgg#13's recovery half).
This also makes the previously-vacuous test assertable. It now asserts the generated
page appears, and is verified deletion-sensitive: removing the synthesis fails both
new tests. `declared: false` (no schema, or plugin disabled) renders an explanation
rather than an empty form.
Gates: typecheck 0 errors; web 1319 passed; server 1672 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 17, 2026
Sol spec review MUST pingdotgg#13 (detection half). The store recorded a schema fingerprint
from the first commit but nothing ever read it back, so an upgrade that changed a
plugin's schema under already-stored values went undetected — the values were read
as if they still matched, which is exactly the silent misread the fingerprint exists
to prevent.
readDraft now surfaces the fingerprint that produced the stored values, and the
settings RPC compares it against the plugin's CURRENT schema. A mismatch marks the
draft incompatible, which the settings page already renders as "these settings need
attention" with the form open for repair.
The stored data is preserved on mismatch, not discarded: the user may need to read
the old values to reconstruct the new ones, and a plugin that briefly downgrades
should not lose its config. Recovery is a valid save, which rewrites both the values
and the fingerprint.
Tests verified deletion-sensitive: returning a null fingerprint from readDraft fails
both fingerprint tests.
Remaining for MUST pingdotgg#13: hostApi.settings.get should fail InvalidStored on drift
rather than relying on decode alone. The decode already fails for an incompatible
shape in the common case; the fingerprint makes it explicit. Tracked, not claimed.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 17, 2026
Sol spec review MUST pingdotgg#13, plugin-read half. hostApi.settings.get now rejects stored
values whose fingerprint does not match the plugin's current schema, instead of
relying on decode to notice.
Decode is not sufficient and that is the whole point: a widening change (adding an
optional field, relaxing a filter) still decodes cleanly, so the plugin would
silently run on values written for a shape it no longer declares. Comparing the
fingerprint makes the mismatch the fact being checked rather than a side effect of
decoding. The error carries no values — only the plugin id.
KNOWN GAP, stated rather than implied: this path has no test. I wrote one with a
fixture that probes settings.get from a service and records the Exit tag to its data
dir; the probe did not get scheduled within the test's wait, and the "<probe never
ran>" guard I built into it caught that rather than letting it pass vacuously against
whatever I asserted. Rather than ship a test that cannot reliably observe the
behaviour — or one that asserts only "the plugin activated", which would pass with
this check deleted — the tests are removed and the gap recorded.
Store-level fingerprint behaviour IS covered and verified deletion-sensitive
(PluginSettingsStore.test.ts); what is untested is specifically the capability read
path rejecting on drift. Needs a fixture harness that can await a forked service.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 30, 2026
Makes declarative settings real: a plugin declares a schema and the HOST renders the
form. The plugin ships no form, no RPC, no storage.
The loader now synthesises a settings page from `definition.settings`, so the
declaration is no longer inert (the previous commit shipped the declaration with
nothing consuming it). Generated before register(), so a plugin declaring both gets
its generated page first, then its own; the page id is fixed so a plugin's own
registerSettingsPage cannot shadow it.
PluginSettingsPage wires the concurrency story end to end: the draft carries a
revision, every save sends it back as expectedRevision, and a stale save is rejected
by the server rather than silently clobbering another tab. The server's message is
surfaced verbatim because "does not match the schema" and "changed elsewhere" need
different actions from the user.
The form opens on incompatible stored data and says so, rather than refusing — that
is exactly when the user needs it to repair the values, and the stored data is
preserved until a valid save (Sol spec review MUST pingdotgg#13's recovery half).
This also makes the previously-vacuous test assertable. It now asserts the generated
page appears, and is verified deletion-sensitive: removing the synthesis fails both
new tests. `declared: false` (no schema, or plugin disabled) renders an explanation
rather than an empty form.
Gates: typecheck 0 errors; web 1319 passed; server 1672 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 30, 2026
Sol spec review MUST pingdotgg#13 (detection half). The store recorded a schema fingerprint
from the first commit but nothing ever read it back, so an upgrade that changed a
plugin's schema under already-stored values went undetected — the values were read
as if they still matched, which is exactly the silent misread the fingerprint exists
to prevent.
readDraft now surfaces the fingerprint that produced the stored values, and the
settings RPC compares it against the plugin's CURRENT schema. A mismatch marks the
draft incompatible, which the settings page already renders as "these settings need
attention" with the form open for repair.
The stored data is preserved on mismatch, not discarded: the user may need to read
the old values to reconstruct the new ones, and a plugin that briefly downgrades
should not lose its config. Recovery is a valid save, which rewrites both the values
and the fingerprint.
Tests verified deletion-sensitive: returning a null fingerprint from readDraft fails
both fingerprint tests.
Remaining for MUST pingdotgg#13: hostApi.settings.get should fail InvalidStored on drift
rather than relying on decode alone. The decode already fails for an incompatible
shape in the common case; the fingerprint makes it explicit. Tracked, not claimed.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 30, 2026
Sol spec review MUST pingdotgg#13, plugin-read half. hostApi.settings.get now rejects stored
values whose fingerprint does not match the plugin's current schema, instead of
relying on decode to notice.
Decode is not sufficient and that is the whole point: a widening change (adding an
optional field, relaxing a filter) still decodes cleanly, so the plugin would
silently run on values written for a shape it no longer declares. Comparing the
fingerprint makes the mismatch the fact being checked rather than a side effect of
decoding. The error carries no values — only the plugin id.
KNOWN GAP, stated rather than implied: this path has no test. I wrote one with a
fixture that probes settings.get from a service and records the Exit tag to its data
dir; the probe did not get scheduled within the test's wait, and the "<probe never
ran>" guard I built into it caught that rather than letting it pass vacuously against
whatever I asserted. Rather than ship a test that cannot reliably observe the
behaviour — or one that asserts only "the plugin activated", which would pass with
this check deleted — the tests are removed and the gap recorded.
Store-level fingerprint behaviour IS covered and verified deletion-sensitive
(PluginSettingsStore.test.ts); what is untested is specifically the capability read
path rejecting on drift. Needs a fixture harness that can await a forked service.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 30, 2026
Makes declarative settings real: a plugin declares a schema and the HOST renders the
form. The plugin ships no form, no RPC, no storage.
The loader now synthesises a settings page from `definition.settings`, so the
declaration is no longer inert (the previous commit shipped the declaration with
nothing consuming it). Generated before register(), so a plugin declaring both gets
its generated page first, then its own; the page id is fixed so a plugin's own
registerSettingsPage cannot shadow it.
PluginSettingsPage wires the concurrency story end to end: the draft carries a
revision, every save sends it back as expectedRevision, and a stale save is rejected
by the server rather than silently clobbering another tab. The server's message is
surfaced verbatim because "does not match the schema" and "changed elsewhere" need
different actions from the user.
The form opens on incompatible stored data and says so, rather than refusing — that
is exactly when the user needs it to repair the values, and the stored data is
preserved until a valid save (Sol spec review MUST pingdotgg#13's recovery half).
This also makes the previously-vacuous test assertable. It now asserts the generated
page appears, and is verified deletion-sensitive: removing the synthesis fails both
new tests. `declared: false` (no schema, or plugin disabled) renders an explanation
rather than an empty form.
Gates: typecheck 0 errors; web 1319 passed; server 1672 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 30, 2026
Sol spec review MUST pingdotgg#13 (detection half). The store recorded a schema fingerprint
from the first commit but nothing ever read it back, so an upgrade that changed a
plugin's schema under already-stored values went undetected — the values were read
as if they still matched, which is exactly the silent misread the fingerprint exists
to prevent.
readDraft now surfaces the fingerprint that produced the stored values, and the
settings RPC compares it against the plugin's CURRENT schema. A mismatch marks the
draft incompatible, which the settings page already renders as "these settings need
attention" with the form open for repair.
The stored data is preserved on mismatch, not discarded: the user may need to read
the old values to reconstruct the new ones, and a plugin that briefly downgrades
should not lose its config. Recovery is a valid save, which rewrites both the values
and the fingerprint.
Tests verified deletion-sensitive: returning a null fingerprint from readDraft fails
both fingerprint tests.
Remaining for MUST pingdotgg#13: hostApi.settings.get should fail InvalidStored on drift
rather than relying on decode alone. The decode already fails for an incompatible
shape in the common case; the fingerprint makes it explicit. Tracked, not claimed.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 30, 2026
Sol spec review MUST pingdotgg#13, plugin-read half. hostApi.settings.get now rejects stored
values whose fingerprint does not match the plugin's current schema, instead of
relying on decode to notice.
Decode is not sufficient and that is the whole point: a widening change (adding an
optional field, relaxing a filter) still decodes cleanly, so the plugin would
silently run on values written for a shape it no longer declares. Comparing the
fingerprint makes the mismatch the fact being checked rather than a side effect of
decoding. The error carries no values — only the plugin id.
KNOWN GAP, stated rather than implied: this path has no test. I wrote one with a
fixture that probes settings.get from a service and records the Exit tag to its data
dir; the probe did not get scheduled within the test's wait, and the "<probe never
ran>" guard I built into it caught that rather than letting it pass vacuously against
whatever I asserted. Rather than ship a test that cannot reliably observe the
behaviour — or one that asserts only "the plugin activated", which would pass with
this check deleted — the tests are removed and the gap recorded.
Store-level fingerprint behaviour IS covered and verified deletion-sensitive
(PluginSettingsStore.test.ts); what is untested is specifically the capability read
path rejecting on drift. Needs a fixture harness that can await a forked service.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 31, 2026
Makes declarative settings real: a plugin declares a schema and the HOST renders the
form. The plugin ships no form, no RPC, no storage.
The loader now synthesises a settings page from `definition.settings`, so the
declaration is no longer inert (the previous commit shipped the declaration with
nothing consuming it). Generated before register(), so a plugin declaring both gets
its generated page first, then its own; the page id is fixed so a plugin's own
registerSettingsPage cannot shadow it.
PluginSettingsPage wires the concurrency story end to end: the draft carries a
revision, every save sends it back as expectedRevision, and a stale save is rejected
by the server rather than silently clobbering another tab. The server's message is
surfaced verbatim because "does not match the schema" and "changed elsewhere" need
different actions from the user.
The form opens on incompatible stored data and says so, rather than refusing — that
is exactly when the user needs it to repair the values, and the stored data is
preserved until a valid save (Sol spec review MUST pingdotgg#13's recovery half).
This also makes the previously-vacuous test assertable. It now asserts the generated
page appears, and is verified deletion-sensitive: removing the synthesis fails both
new tests. `declared: false` (no schema, or plugin disabled) renders an explanation
rather than an empty form.
Gates: typecheck 0 errors; web 1319 passed; server 1672 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 31, 2026
Sol spec review MUST pingdotgg#13 (detection half). The store recorded a schema fingerprint
from the first commit but nothing ever read it back, so an upgrade that changed a
plugin's schema under already-stored values went undetected — the values were read
as if they still matched, which is exactly the silent misread the fingerprint exists
to prevent.
readDraft now surfaces the fingerprint that produced the stored values, and the
settings RPC compares it against the plugin's CURRENT schema. A mismatch marks the
draft incompatible, which the settings page already renders as "these settings need
attention" with the form open for repair.
The stored data is preserved on mismatch, not discarded: the user may need to read
the old values to reconstruct the new ones, and a plugin that briefly downgrades
should not lose its config. Recovery is a valid save, which rewrites both the values
and the fingerprint.
Tests verified deletion-sensitive: returning a null fingerprint from readDraft fails
both fingerprint tests.
Remaining for MUST pingdotgg#13: hostApi.settings.get should fail InvalidStored on drift
rather than relying on decode alone. The decode already fails for an incompatible
shape in the common case; the fingerprint makes it explicit. Tracked, not claimed.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 31, 2026
Sol spec review MUST pingdotgg#13, plugin-read half. hostApi.settings.get now rejects stored
values whose fingerprint does not match the plugin's current schema, instead of
relying on decode to notice.
Decode is not sufficient and that is the whole point: a widening change (adding an
optional field, relaxing a filter) still decodes cleanly, so the plugin would
silently run on values written for a shape it no longer declares. Comparing the
fingerprint makes the mismatch the fact being checked rather than a side effect of
decoding. The error carries no values — only the plugin id.
KNOWN GAP, stated rather than implied: this path has no test. I wrote one with a
fixture that probes settings.get from a service and records the Exit tag to its data
dir; the probe did not get scheduled within the test's wait, and the "<probe never
ran>" guard I built into it caught that rather than letting it pass vacuously against
whatever I asserted. Rather than ship a test that cannot reliably observe the
behaviour — or one that asserts only "the plugin activated", which would pass with
this check deleted — the tests are removed and the gap recorded.
Store-level fingerprint behaviour IS covered and verified deletion-sensitive
(PluginSettingsStore.test.ts); what is untested is specifically the capability read
path rejecting on drift. Needs a fixture harness that can await a forked service.
Gates: typecheck 0 errors; server 1675 passed.
aorwall added a commit to aorwall/t3code that referenced this pull request Aug 2, 2026
* docs: spec for preview servers in the T3 Code web app
Approved spec covering the Servers right-panel view, Moatless-owned preview
tabs backed by Redis, and an iframe renderer that lets the existing browser
panel work outside Electron.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat: add the servers.* contract group
Three reads over the servers a thread's environment declares: list them, watch
their status, and follow one's log. Field names are taken verbatim from the
host's own server record so the server side is a rename-free serialization.
Nothing here starts, stops or reconfigures a server; those methods are
specified elsewhere and deliberately out of scope.
The preview module docstring said the preview is desktop-only and that the
desktop renderer mediates. Both stop being true in the same change, so the
docstring now describes the surface by capability rather than by client.
* feat: carry the servers.* group through the client runtime and reference server
Client-side atoms for the three reads, with the log subscription folding
lines into a bounded buffer so a remounted panel keeps what it received
rather than starting over.
The reference server declares no thread servers - it runs threads on the
machine it is on - so it answers the list empty and both subscriptions
silent. A hosted environment answers the same three methods with real data.
Also splits the preview runtime capability in two. One boolean was answering
both "can this runtime show a page" and "can this runtime read the page it
shows", which are the same question only on the desktop app.
* feat: host preview pages in a sandboxed frame on the web
The web app could not show a task's page at all. It can now: one frame per
open tab, mounted at the app root so it survives the panel collapsing, and
positioned by the same surface rect the desktop webview uses.
The frame is driven and never read. Navigation writes to the server and the
frame follows; refresh replaces the element because a cross-origin frame has
no reload and reassigning src would grow the parent's history; back and
forward are absent rather than dead.
Two things a frame cannot report, the panel says from elsewhere. A page that
is not there is explained by what its server says about itself, which is
better than a net error - "installing" is an answer no net error carries. A
page that renders nothing while its server says it is running gets a hint
offered as a hint, because a frame-ancestors refusal and the preview host's
own 401 look identical from outside and neither fires an event.
* feat: add a Servers view to the right panel
One row per server the thread's environment declares, with its status kept
current by the subscription, its log on demand, and an Open that hands the
URL to a browser tab.
Everything in it is a read - a row that says failed offers its log and no
button. Restarting a server is a write and lands elsewhere.
The right panel's persisted state moves to version 8 for the new surface
kind, and its migration now drops surfaces whose kind this build does not
know. That is what makes the version bump safe to downgrade away from: the
rest of the workspace survives and only the unknown tab is lost.
* test: cover the browser preview surface and the servers view
Two products share these schemas and no test process, so the seam is a set
of real Moatless responses checked in here and decoded by the schemas
themselves. When its projection changes the fixture changes with it in one
commit, and the decode test is what fails if the two drift.
The rest covers what the change actually promises: the capability answers
three runtimes, the chrome row omits controls rather than disabling them,
the frame re-keys instead of reassigning src, exactly one browser host
renders, and the panel state survives the version bump while dropping a kind
this build does not know.
* fix: state the environment's absence rather than implying it from a row
The never-provisioned fixture claimed a server is listed as `stopped` with
no URL. It is not. Moatless resolves status config-first from a NotFound
pod, which falls back to `starting` with the ingress URL the port will have
— so the panel showed `starting` forever for an environment that does not
exist, and offered an Open button pointing at a 502.
The fixture now carries what the backend produces, and the panel states the
environment's own status above the list instead of leaving it to be inferred
from rows that cannot say it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: track the fork's own delta, not just what upstream does to it
The policy could answer "who wins this conflict" but not "what did we change",
and the second is the question you are actually asking when git stops on a line
you do not recognise. The path table only ever listed files we expected to fight
over — a much smaller set than the files we changed. The whole thread-servers
group, around forty files, appeared nowhere.
§3 is that missing list, and a fifth hazard in the preamble names the failure it
prevents: a conflict resolved toward upstream because nobody could tell our line
was deliberate. Two consequences: the checklist asks for a row in the same commit
as the change rather than at the next merge, and reading the inventory is now a
step in resolving a conflict rather than something to remember.
Also adds path-policy rows for the highest-risk of those files —
`apps/server/src/ws.ts` and `RpcAuthorization.ts`, the only upstream server files
the fork touches, where the resolution is to take theirs and re-add three
`servers.*` entries.
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
aorwall pushed a commit to aorwall/t3code that referenced this pull request Aug 12, 2026
The 2026-08-08 upstream merge took 89 minutes. Most of that was not conflict
resolution; it was performing checks that existed only as prose in
docs/fork/upstream-merge-inventory.md, one at a time, and rediscovering two
things the doc could have told me before I started.
Five changes, in the order they pay off.
**The inventory is data now.** docs/fork/inventory.json holds the fork-owned
concerns, path policy, fork inventory, tripwires, deliberately deleted upstream
paths, off-repository state and convergence entries. The markdown keeps only
what a check cannot hold — the four re-application deltas, where the
unsupported-method set comes from, and the reasoning behind the path policy
rule — and drops from 784 lines to 243.
**The checks run.** Five dependency-free scripts under the skill:
- `preflight.mjs` — the range, stale entries, the owned-concern sweep, and the
conflict forecast: every file both sides touched, grouped by the verdict that
resolves it, before the merge starts.
- `inventory-check.mjs` — every inventory path read back out of upstream/main.
This is the one that matters: last merge, upstream moved SidebarV2's content
into Sidebar.tsx and Sidebar.tsx's into LegacySidebar.tsx. Git cannot see a
content swap as a rename — it is a delete paired with a modify, which no -M
threshold detects — so it surfaced as a modify/delete conflict mid-merge.
Run against the pre-merge tree, this check names it in about a second.
- `tripwires.mjs`, `unsupported-methods.mjs`, `verify.mjs`.
Running inventory-check against the current tree immediately found three dead
path-policy entries that had been faithfully transcribed forward through
several merges, for directories deleted in pingdotgg#13 and pingdotgg#26.
**Verification is one command, earlier.** `verify.mjs` runs tripwires, the
unsupported-method derivation, format, lint, types and tests — and does not
stop at the first failure, so a formatting nit no longer hides the type errors
behind it. It raises the heap the web suite needs, whose failure mode is
otherwise an exit 137 that reads like a real test failure. The skill now runs
it *before* the documentation steps, because its output is their input.
**Counts come out of prose.** Both snapshots the last merge relied on were
stale. "38 of 87 methods" and "Clerk 4 / pairing 73 / session bootstrap 9" are
replaced by the command that derives them.
**The fork test no longer hard-codes upstream paths.** features.test.ts read
`../components/Sidebar.tsx?raw`; when upstream renamed that file the whole
suite failed to build, at a module path, saying nothing about the gate. It now
reads the guarded files out of inventory.json and reaches them through
import.meta.glob, so a rename fails as a named assertion that says which entry
to re-point — and the test and the merge scripts read the same guards, so they
cannot drift apart.
Verified: fmt, lint, typecheck and 1908 tests pass. `tripwires.mjs` reports one
finding, which is genuine and already tracked — thread-transfer-report.yml went
active on GitHub when the merge branch was pushed and still needs disabling.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
NeilTheFisher pushed a commit to NeilTheFisher/t3code that referenced this pull request Aug 18, 2026
AFLabAI added a commit to AFLabAI/t3code that referenced this pull request Sep 2, 2026
Increase diagnostic visibility by appending complete vp check stdout/stderr to job summary instead of truncating. This exposes all 11 lint errors detected in RUN pingdotgg#13.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Sign up for freeto 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

@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: Commit, Push and Create PR actions - #13

Merged
juliusmarminge merged 18 commits into
mainfrom
codex/add-github-commit-push-ui
Feb 12, 2026
Merged

feat: Commit, Push and Create PR actions#13
juliusmarminge merged 18 commits into
mainfrom
codex/add-github-commit-push-ui

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Feb 12, 2026

Copy link
Copy Markdown
Member

Open with Devin

Summary by CodeRabbit

  • New Features

    • AI-powered generation for commit messages and PR title/body.
    • New Git core & manager with stacked workflows (commit, commit & push, commit & push & create/open PR), richer status details, and WebSocket/native endpoints to run Git status and actions.
    • UI: Git actions menu with real-time status, action execution, notices and error handling.
  • Chores

    • Robust process runner with buffer and timeout safeguards.
  • Tests

    • Extensive end-to-end and unit tests covering Git flows, PR lifecycle, manager behavior, and contract schemas.

@coderabbitai

coderabbitaiBot commented Feb 12, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds a Git subsystem: process runner with buffer/timeouts, Git core service, GitManager orchestration, Codex-based commit/PR generation, WS API + server wiring, client UI integration, contracts/schemas for stacked git actions, and extensive tests.

Changes

Cohort / File(s)Summary
Text Generation Service
apps/server/src/coreServices.ts, apps/server/src/codexTextGenerator.ts
Defines TextGenerationService types and implements CodexTextGenerator with JSON schemas, validation/parsers, temp-file helpers, runCodexJson orchestration, and methods to generate commit messages and PR content.
Process Execution
apps/server/src/processRunner.ts
Adds runProcess with configurable maxBufferBytes, stdout/stderr accumulation and byte limits, timeout handling (SIGTERM→SIGKILL), spawn/exit normalization, and detailed error types.
Git Core Service
apps/server/src/git.ts
Introduces GitCoreService and helpers: enriched status/statusDetails, prepareCommitContext, commit, pushCurrentBranch, readRangeContext, readConfigValue, runGit helpers, and upstream/default-branch logic.
GitManager Orchestration
apps/server/src/gitManager.ts
Adds GitManager coordinating gitCore, processRunner, Codex text generation, and gh interactions for stacked actions (commit, push, PR), PR discovery/creation, temp-file PR bodies, and error normalization.
Server WS Integration & Tests
apps/server/src/wsServer.ts, apps/server/src/wsServer.test.ts
Wires GitManager into server options, exposes WS methods git.status and git.runStackedAction, and updates tests to inject/validate gitManager delegation and error propagation.
Tests: GitManager & GitCore
apps/server/src/gitManager.test.ts, apps/server/src/git.test.ts
Adds end-to-end tests covering stacked actions, GH CLI scenarios, upstream behavior, detached HEAD, auth errors, and GitCoreService behaviors.
Client Integration
apps/web/src/wsNativeApi.ts, apps/web/src/components/ChatView.tsx
Extends native API with git.status and git.runStackedAction; integrates Git actions menu and UI state into ChatView with status polling, action execution, and notices.
Contracts / IPC / WS Methods & Tests
packages/contracts/src/git.ts, packages/contracts/src/git.test.ts, packages/contracts/src/ipc.ts, packages/contracts/src/ws.ts
Adds Zod schemas/types for git status and stacked actions, updates NativeApi.git signatures, expands WS_METHODS with git.status and git.runStackedAction, and adds schema tests.

Sequence Diagram(s)

sequenceDiagram
participant Client as Client (ChatView)
participant WS as WebSocket Server
participant GM as GitManager
participant GC as GitCoreService
participant Proc as ProcessRunner
participant Codex as Codex Service
participant GH as GitHub CLI
Client->>WS: git.runStackedAction(action, cwd)
WS->>GM: runStackedAction()
rect rgba(100,150,200,0.5)
Note over GM: Commit Step
GM->>GC: prepareCommitContext(cwd)
GC-->>GM: stagedSummary, stagedPatch
GM->>Codex: generateCommitMessage(diff)
Codex-->>GM: subject, body
GM->>Proc: git commit -m "..."
Proc-->>GM: commit result
end
rect rgba(100,200,150,0.5)
Note over GM: Push Step (if requested)
GM->>GC: pushCurrentBranch(cwd, upstream?)
GC-->>GM: push result
end
rect rgba(200,150,100,0.5)
Note over GM: PR Step (if requested)
GM->>GC: readRangeContext(base, head)
GC-->>GM: commitSummary, diffSummary, diffPatch
GM->>Codex: generatePrContent(rangeContext)
Codex-->>GM: title, body
GM->>GH: gh pr list --head branch
GH-->>GM: existing PRs
alt PR exists
GM->>GH: gh pr view PR_NUMBER
GH-->>GM: PR details
else
GM->>GH: gh pr create --title "..." --body file://tmp
GH-->>GM: new PR info
end
end
GM-->>WS: GitRunStackedActionResult
WS-->>Client: result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 2.56% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title 'feat: Commit, Push and Create PR actions' accurately captures the main feature added: three new Git workflow actions integrated into the UI and backend services.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch codex/add-github-commit-push-ui

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps

greptile-appsBot commented Feb 12, 2026

Copy link
Copy Markdown

Greptile Overview

Greptile Summary

This PR implements comprehensive Git stacked actions (commit, push, create PR) with AI-powered text generation for commit messages and PR content. The implementation spans server-side orchestration, robust process execution, WebSocket/IPC integration, and a polished React UI with real-time progress tracking.

Key changes:

  • New GitManager orchestrates stacked workflows (commit → push → PR) with proper error handling and GitHub CLI integration
  • GitCoreService provides low-level Git operations using direct spawn (no shell), with buffer management and timeout safeguards
  • CodexTextGenerator generates commit messages and PR content via Codex CLI with structured JSON output schemas
  • ProcessRunner implements robust subprocess execution with truncation support, graceful termination, and buffer limit enforcement
  • GitActionsControl UI component provides modal-based workflow with step-by-step progress, custom commit messages, and error states
  • Comprehensive test coverage across unit tests (git.test.ts, processRunner.test.ts) and integration tests (gitManager.test.ts)
  • WebSocket and IPC layers extended to expose git.status and git.runStackedAction methods to both web and desktop clients

Architecture aligns with project priorities:

  • Performance: Direct spawn without shell overhead, buffer limits prevent memory issues
  • Reliability: Proper timeout handling, graceful degradation (PR lookup is best-effort), temp file cleanup
  • Predictable behavior: Structured status tracking, deterministic base branch resolution, comprehensive error normalization

Confidence Score: 5/5

  • This PR is safe to merge with high confidence
  • The implementation demonstrates strong engineering discipline: comprehensive test coverage (490 lines in gitManager.test.ts alone), proper error handling with normalized error messages, robust subprocess management with buffer limits and timeouts, dependency injection for testability, and alignment with project priorities (performance, reliability, predictable behavior). The only noted issue is duplicate commit message sanitization which is cosmetic and doesn't affect functionality.
  • No files require special attention

Important Files Changed

FilenameOverview
apps/server/src/git.tsAdds comprehensive Git operations with robust error handling, proper spawn arguments, and buffer management for status, commit, push, and range context operations
apps/server/src/gitManager.tsImplements high-level orchestration for stacked Git workflows (commit, push, PR) with AI text generation, proper dependency injection, and GitHub CLI integration
apps/server/src/codexTextGenerator.tsImplements AI-powered text generation for commit messages and PR content using Codex CLI with structured JSON output, proper sanitization, and temp file cleanup
apps/server/src/processRunner.tsAdds robust process execution with buffer limit enforcement, timeout handling, graceful termination (SIGTERM then SIGKILL), and truncation support
apps/server/src/wsServer.tsIntegrates GitManager into WebSocket server to expose git.status and git.runStackedAction methods via WS protocol
packages/contracts/src/git.tsExtends Git contracts with Zod schemas for stacked action API (status, commit, push, PR) with comprehensive validation rules
apps/web/src/components/GitActionsControl.tsxImplements comprehensive Git actions UI with modal workflow, real-time progress tracking, step-by-step execution, error handling, and PR link opening

Sequence Diagram

sequenceDiagram
participant User
participant GitActionsControl
participant NativeApi
participant GitManager
participant GitCore
participant CodexTextGenerator
participant GitCLI
participant GitHubCLI
User->>GitActionsControl: Click "Commit and create PR"
GitActionsControl->>GitActionsControl: Open modal, set action
User->>GitActionsControl: Confirm action
GitActionsControl->>NativeApi: git.runStackedAction(commit)
NativeApi->>GitManager: runStackedAction(commit)
GitManager->>GitCore: statusDetails(cwd)
GitCore->>GitCLI: git status --porcelain=2
GitCLI-->>GitCore: status output
GitCore-->>GitManager: branch, upstream info
GitManager->>GitCore: prepareCommitContext(cwd)
GitCore->>GitCLI: git add -A
GitCore->>GitCLI: git diff --cached
GitCLI-->>GitCore: staged changes
GitCore-->>GitManager: stagedSummary, stagedPatch
GitManager->>CodexTextGenerator: generateCommitMessage()
CodexTextGenerator->>CodexTextGenerator: Write temp schema file
CodexTextGenerator->>GitCLI: codex exec --output-schema
GitCLI-->>CodexTextGenerator: JSON response
CodexTextGenerator->>CodexTextGenerator: Parse, sanitize, cleanup
CodexTextGenerator-->>GitManager: {subject, body}
GitManager->>GitCore: commit(cwd, subject, body)
GitCore->>GitCLI: git commit -m subject -m body
GitCLI-->>GitCore: success
GitCore-->>GitManager: {commitSha}
GitManager-->>NativeApi: commit result
NativeApi-->>GitActionsControl: Update progress: commit completed
GitActionsControl->>NativeApi: git.runStackedAction(commit_push)
NativeApi->>GitManager: runStackedAction(commit_push)
GitManager->>GitCore: pushCurrentBranch(cwd)
GitCore->>GitCLI: git push -u origin branch
GitCLI-->>GitCore: success
GitCore-->>GitManager: {status: pushed, branch}
GitManager-->>NativeApi: push result
NativeApi-->>GitActionsControl: Update progress: push completed
GitActionsControl->>NativeApi: git.runStackedAction(commit_push_pr)
NativeApi->>GitManager: runStackedAction(commit_push_pr)
GitManager->>GitHubCLI: gh pr list --head branch
GitHubCLI-->>GitManager: [] (no existing PR)
GitManager->>GitCore: readRangeContext(cwd, baseBranch)
GitCore->>GitCLI: git log, git diff
GitCLI-->>GitCore: commit history, diff
GitCore-->>GitManager: rangeContext
GitManager->>CodexTextGenerator: generatePrContent()
CodexTextGenerator->>GitCLI: codex exec --output-schema
GitCLI-->>CodexTextGenerator: JSON response
CodexTextGenerator-->>GitManager: {title, body}
GitManager->>GitHubCLI: gh pr create --title --body-file
GitHubCLI-->>GitManager: PR URL
GitManager->>GitHubCLI: gh pr view --web
GitHubCLI-->>GitManager: success
GitManager-->>NativeApi: pr result
NativeApi-->>GitActionsControl: Update progress: PR created
GitActionsControl->>User: Show completion with PR link
Loading

Last reviewed commit: dd92084

@greptile-appsgreptile-appsBot 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.

14 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment threadapps/server/src/gitManager.ts

@coderabbitaicoderabbitaiBot 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.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@apps/server/src/processRunner.ts`:
- Around line 128-131: Attach an 'error' listener to the child's stdin and use
the write callback to handle possible write errors before calling end: in
processRunner.ts, before calling child.stdin.write(...) register
child.stdin.once("error", err => {/* reject/forward error or cleanup and ensure
promise rejects */}), call child.stdin.write(options.stdin, (err) => { if (err)
{ /* handle/reject/cleanup */ } child.stdin.end(); }); and remove the
unconditional child.stdin.end() so we only end after the write completes; ensure
any error handling forwards the error to the same rejection/cleanup path used by
child.once("error") for the spawned process.
🧹 Nitpick comments (2)
packages/contracts/src/git.test.ts (1)

11-71: LGTM!

The tests provide good coverage for schema validation, including whitespace trimming and nested field parsing.

Consider adding negative test cases to verify that invalid inputs are rejected (e.g., invalid action strings, missing required fields). This would strengthen the contract validation.

,

apps/server/src/codexTextGenerator.ts (1)

84-103: Consider consolidating duplicate sanitization logic.

sanitizeCommitSubject here (lines 84-95) and sanitizeCommitMessage in gitManager.ts (lines 99-110) perform nearly identical operations: extracting the first line, removing trailing periods, and truncating to 72 characters. This duplication could lead to divergent behavior over time.

Consider extracting a shared utility or having gitManager.ts rely on the already-sanitized output from CodexTextGenerator without additional sanitization.

Comment threadapps/server/src/processRunner.ts
Comment threadapps/server/src/gitManager.ts Outdated
Comment threadapps/server/src/processRunner.ts
Comment threadapps/server/src/processRunner.ts
Comment threadapps/server/src/wsServer.ts
Comment threadapps/server/src/gitManager.ts Outdated
Comment threadapps/web/src/components/ChatView.tsx Outdated
Comment threadapps/server/src/codexTextGenerator.ts Outdated
@macroscopeapp

macroscopeappBot commented Feb 12, 2026

Copy link
Copy Markdown
Contributor

Add Commit, Push, and Create PR actions by introducing GitCoreService, GitManager.runStackedAction, and a web GitActionsControl wired through WS and IPC APIs

Implements stacked git workflows across server and web: adds GitCoreService for git operations, CodexTextGenerator for commit/PR text, and GitManager orchestration; exposes git.status and git.runStackedAction over WebSocket; adds desktop bridge shell.openExternal; and introduces a GitActionsControl UI to run commit/push/PR. Terminal spawning gains multi-shell fallback and runProcess provides standardized subprocess handling. See apps/server/src/gitManager.ts, apps/server/src/git.ts, apps/server/src/codexTextGenerator.ts, and apps/web/src/components/GitActionsControl.tsx.

📍Where to Start

Start with the orchestration entrypoint GitManager.runStackedAction in apps/server/src/gitManager.ts, then review GitCoreService in apps/server/src/git.ts and the web client GitActionsControl in apps/web/src/components/GitActionsControl.tsx.


Macroscope summarized dd92084.

Co-authored-by: codex <codex@users.noreply.github.com>
Comment threadapps/server/src/gitManager.ts

@coderabbitaicoderabbitaiBot 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.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@apps/web/src/components/ChatView.tsx`:
- Around line 395-424: When a git status fetch succeeds we need to clear any
previous error so the error banner doesn't persist; inside the load async
function (the one that calls api.git.status with gitCwd) after successfully
calling setGitStatus(nextStatus) also call setGitActionError(null) (guarded by
the same !cancelled check) so successful refreshes remove stale errors; update
the useEffect's load success branch in ChatView.tsx (the load function /
useEffect that references api, gitCwd, setGitStatus, setGitActionError)
accordingly.

Comment threadapps/web/src/components/ChatView.tsx Outdated
Comment threadapps/server/src/git.ts
Comment threadapps/server/src/git.ts Outdated

@coderabbitaicoderabbitaiBot 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.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@apps/server/src/git.test.ts`:
- Around line 460-466: The test invokes git with a single-quoted remote path
which fails on Windows; update the call that runs git(tmp.path, `remote add
origin '${remote.path}'`) to wrap the path using JSON.stringify(remote.path)
(i.e., produce a double-quoted, escaped string) so Windows cmd handles spaces
correctly; modify the test where makeTmpDir()/remote and the git(...) invocation
are used (see git(tmp.path, `remote add origin ...`), initRepoWithCommit,
createGitBranch) to pass the JSON.stringify-wrapped path instead of single
quotes.
In `@apps/server/src/git.ts`:
- Around line 245-251: The current early-return treats a branch as
"skipped_up_to_date" when details.hasUpstream && details.aheadCount === 0 even
if it is behind; update the condition to also require details.behindCount === 0
so we only mark truly up-to-date branches. Change the if that checks
details.hasUpstream and details.aheadCount to: details.hasUpstream &&
details.aheadCount === 0 && details.behindCount === 0 (leaving the returned
object with branch and optional upstreamBranch unchanged).
- Around line 129-143: The timeout check must be unconditional: in
runGitOrThrow, always throw when result.timedOut by calling
normalizeGitExecutionError(args, result) (or similar) before considering
options.allowNonZeroExit; then keep the existing non-zero exit handling for
result.code when options.allowNonZeroExit is false. Update the logic in
runGitOrThrow (referencing runGitOrThrow, RunGitOptions,
options.allowNonZeroExit, result.timedOut, result.code, and
normalizeGitExecutionError) so timeouts are detected and thrown unconditionally
while allowing suppressed non-zero exit codes only when appropriate.
In `@apps/server/src/gitManager.ts`:
- Around line 402-423: Both runGh and runGhStdout currently call this.run("gh",
args, { cwd }) and can hang; add an explicit timeoutMs option to those calls.
Define a clear constant (e.g. GH_CLI_TIMEOUT_MS = 30_000) near the top of the
module and pass it into this.run as { cwd, timeoutMs: GH_CLI_TIMEOUT_MS } in
both runGh and runGhStdout so gh CLI invocations time out predictably. Ensure
the constant is used in both functions and adjust any types if needed to match
ProcessRunOptions.
🧹 Nitpick comments (1)
apps/server/src/gitManager.ts (1)

175-183: If no commit is created, skip push/PR to avoid empty actions.

When runCommitStep returns skipped_no_changes, the current flow still pushes and can attempt PR creation. That can create confusing “no‑op” PRs or unnecessary network calls.

Comment threadapps/server/src/git.test.ts
Comment threadapps/server/src/git.ts
Comment threadapps/server/src/git.ts Outdated
Comment threadapps/server/src/gitManager.ts
@juliusmarmingejuliusmarminge changed the title Add stacked GitHub action workflowGithubFeb 12, 2026
@juliusmarmingejuliusmarminge changed the title Githubfeat: Commit, Push and Create PR actionsFeb 12, 2026
Co-authored-by: codex <codex@users.noreply.github.com>
Comment on lines +123 to +128
const fail = (error: Error): void => {
child.kill("SIGTERM");
finalize(() => {
reject(error);
});
};

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.

🟡 Medium

src/processRunner.ts:123 The fail function clears the timeout that would escalate to SIGKILL, so if the child ignores SIGTERM, the parent hangs forever. Consider adding a force-kill timer in fail similar to the timeout logic (lines 108-110).

- const fail = (error: Error): void => {- child.kill("SIGTERM");- finalize(() => {- reject(error);- });+ const fail = (error: Error): void => {+ child.kill("SIGTERM");+ const killTimer = setTimeout(() => {+ child.kill("SIGKILL");+ }, 1_000);+ finalize(() => {+ clearTimeout(killTimer);+ reject(error);+ });

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/processRunner.ts around lines 123-128:
The `fail` function clears the timeout that would escalate to `SIGKILL`, so if the child ignores `SIGTERM`, the parent hangs forever. Consider adding a force-kill timer in `fail` similar to the timeout logic (lines 108-110).

Comment threadapps/server/src/processRunner.ts Outdated
stream: "stdout" | "stderr",
chunk: Buffer | string,
): Error | null => {
const text = chunk.toString();

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.

🟢 Low

src/processRunner.ts:134 Using chunk.toString() per Buffer can split multi‑byte UTF‑8 and corrupt text; consider StringDecoder to assemble stdout/stderr safely.

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/processRunner.ts around line 134:
Using `chunk.toString()` per `Buffer` can split multi‑byte UTF‑8 and corrupt text; consider `StringDecoder` to assemble `stdout`/`stderr` safely.

@coderabbitaicoderabbitaiBot 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/server/src/git.ts (1)

50-86: ⚠️ Potential issue | 🟠 Major

Add output caps to avoid unbounded git command buffering.
runGit accumulates stdout/stderr without limits; large diffs can spike memory and make behavior unpredictable under load. Consider enforcing a max buffer (similar to processRunner) or streaming to disk with a safe cap.

🔧 Suggested fix (bounded output)
+const DEFAULT_GIT_MAX_BUFFER_BYTES = 8 * 1024 * 1024;+-function runGit(args: readonly string[], cwd: string, timeoutMs = 30_000): Promise<TerminalCommandResult> {+function runGit(+ args: readonly string[],+ cwd: string,+ timeoutMs = 30_000,+ maxBufferBytes = DEFAULT_GIT_MAX_BUFFER_BYTES,+): Promise<TerminalCommandResult> {
return new Promise((resolve, reject) => {
const child = spawn("git", args, {
cwd,
env: process.env,
stdio: ["ignore", "pipe", "pipe"],
});
let stdout = "";
let stderr = "";
let timedOut = false;
+ let stdoutBytes = 0;+ let stderrBytes = 0;+ let settled = false;
const timeout = setTimeout(() => {
timedOut = true;
child.kill("SIGTERM");
setTimeout(() => {
if (!child.killed) child.kill("SIGKILL");
}, 1_000).unref();
}, timeoutMs);
+ const fail = (error: Error) => {+ if (settled) return;+ settled = true;+ clearTimeout(timeout);+ child.kill("SIGTERM");+ reject(error);+ };+
child.stdout?.on("data", (chunk: Buffer) => {
- stdout += chunk.toString();+ const text = chunk.toString();+ stdout += text;+ stdoutBytes += Buffer.byteLength(text);+ if (stdoutBytes > maxBufferBytes) {+ fail(+ new Error(+ `${commandLabel(args)} exceeded stdout buffer limit (${maxBufferBytes} bytes).`,+ ),+ );+ }
});
child.stderr?.on("data", (chunk: Buffer) => {
- stderr += chunk.toString();+ const text = chunk.toString();+ stderr += text;+ stderrBytes += Buffer.byteLength(text);+ if (stderrBytes > maxBufferBytes) {+ fail(+ new Error(+ `${commandLabel(args)} exceeded stderr buffer limit (${maxBufferBytes} bytes).`,+ ),+ );+ }
});
child.on("error", (error) => {
clearTimeout(timeout);
reject(error);
});
child.on("close", (code, signal) => {
+ if (settled) return;+ settled = true;
clearTimeout(timeout);
resolve({ stdout, stderr, code: code ?? null, signal: signal ?? null, timedOut });
});
});
}

As per coding guidelines: Maintain predictable behavior under load and during failures (session restarts, reconnects, partial streams).

Comment threadapps/web/src/components/ChatView.tsx Outdated
Co-authored-by: codex <codex@users.noreply.github.com>
await runGitOrThrow(cwd, args, { allowNonZeroExit });
}

async gitStdout(cwd: string, args: readonly string[], allowNonZeroExit = false): Promise<string> {

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.

🟡 Medium

src/git.ts:359gitStdout discards stderr even on success, so the truncation warning from runGit is lost. Consider checking if output was truncated and either throwing an error or returning a flag so callers know the data is incomplete.

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/git.ts around line 359:
`gitStdout` discards `stderr` even on success, so the truncation warning from `runGit` is lost. Consider checking if output was truncated and either throwing an error or returning a flag so callers know the data is incomplete.

},
);

const rawOutput = (await fs.readFile(outputPath, "utf8")).trim();

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.

🟠 High

src/codexTextGenerator.ts:145fs.readFile at line 145 has no size limit, unlike the maxBufferBytes guard on stdout/stderr in runProcess. Consider adding a file size check (via fs.stat) before reading to prevent OOM if codex produces unexpectedly large output.

- const rawOutput = (await fs.readFile(outputPath, "utf8")).trim();+ const MAX_OUTPUT_BYTES = 8 * 1024 * 1024;+ const stat = await fs.stat(outputPath);+ if (stat.size > MAX_OUTPUT_BYTES) {+ throw new Error(`Codex output exceeded size limit (${MAX_OUTPUT_BYTES} bytes).`);+ }+ const rawOutput = (await fs.readFile(outputPath, "utf8")).trim();

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/codexTextGenerator.ts around line 145:
`fs.readFile` at line 145 has no size limit, unlike the `maxBufferBytes` guard on stdout/stderr in `runProcess`. Consider adding a file size check (via `fs.stat`) before reading to prevent OOM if `codex` produces unexpectedly large output.

- include open PR metadata in git status with graceful `gh` failure handling
- centralize git command execution in `GitCoreService` via `runProcess` with truncation support
- add PTY spawn-helper permission fixes, shell fallback retries, and tests for new behavior
}

async createWorktree(input: GitCreateWorktreeInput): Promise<GitCreateWorktreeResult> {
const sanitizedBranch = input.newBranch.replace(/\//g, "-");

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.

🟡 Medium

src/git.ts:382 Default worktreePath can collide across repos and branches (sanitized names and shared basenames). Suggest deriving a unique, deterministic path (e.g., hash of full repo path + branch) or document if intentional.

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/git.ts around line 382:
Default `worktreePath` can collide across repos and branches (sanitized names and shared basenames). Suggest deriving a unique, deterministic path (e.g., hash of full repo path + branch) or document if intentional.

args.push("-m", trimmedBody);
}
await this.git(cwd, args);
const commitSha = trimStdout(await this.gitStdout(cwd, ["rev-parse", "HEAD"]));

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.

🟢 Low

src/git.ts:256 Race condition: another commit between git commit and git rev-parse HEAD could return the wrong SHA. Consider using git rev-parse HEAD output from the commit command itself, or use git commit --porcelain to get the SHA atomically.

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/git.ts around line 256:
Race condition: another commit between `git commit` and `git rev-parse HEAD` could return the wrong SHA. Consider using `git rev-parse HEAD` output from the commit command itself, or use `git commit --porcelain` to get the SHA atomically.

const worktreeMap = new Map<string, string>();
if (worktreeList.code === 0) {
let currentPath: string | null = null;
for (const line of worktreeList.stdout.split("\n")) {

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.

🟡 Medium

src/git.ts:347 On Windows, split("\n") leaves trailing \r in paths, causing fs.existsSync to fail. Consider using split(/\r?\n/) instead.

Suggested change
for(constlineofworktreeList.stdout.split("\n")){
for(constlineofworktreeList.stdout.split(/\r?\n/)){

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/git.ts around line 347:
On Windows, `split("\n")` leaves trailing `\r` in paths, causing `fs.existsSync` to fail. Consider using `split(/\r?\n/)` instead.

}

async removeWorktree(input: GitRemoveWorktreeInput): Promise<void> {
await executeGit(input.cwd, ["worktree", "remove", input.path], {

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.

🟢 Low

src/git.ts:402 Consider adding -- before input.path to prevent paths starting with - from being interpreted as git options.

Suggested change
awaitexecuteGit(input.cwd,["worktree","remove",input.path],{
awaitexecuteGit(input.cwd,["worktree","remove","--",input.path],{

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/git.ts around line 402:
Consider adding `--` before `input.path` to prevent paths starting with `-` from being interpreted as git options.

- Replace custom inline SVGs with Lucide commit, push, and GitHub icons
- Keep git action behavior unchanged while applying minor style cleanup
Comment threadapps/web/src/components/ChatView.tsx Outdated
juliusmarmingeand others added 3 commits February 12, 2026 12:22
- Add a Git action confirmation modal with live commit/push/PR progress states
- Accept optional `commitMessage` input and skip AI message generation when provided
- Expand server and contracts tests for custom commit message handling
Co-authored-by: codex <codex@users.noreply.github.com>
- Move git menu, modal, and stacked action logic out of `ChatView`
- Render a new `GitActionsControl` wired with `api` and `gitCwd`
- Split Git actions into context-aware Commit, Push, and PR menu items
- Add modal action selection with clearer availability and disabled-state guidance
- Migrate git status and immediate actions to React Query and add a custom GitHub icon
Co-authored-by: codex <codex@users.noreply.github.com>
@coderabbitaicoderabbitaiBot mentioned this pull request Feb 15, 2026
jjalangtry pushed a commit to jjalangtry/t3code that referenced this pull request Mar 16, 2026
dcherrera pushed a commit to dcherrera/t3code that referenced this pull request Jul 7, 2026
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 16, 2026
Makes declarative settings real: a plugin declares a schema and the HOST renders the
form. The plugin ships no form, no RPC, no storage.
The loader now synthesises a settings page from `definition.settings`, so the
declaration is no longer inert (the previous commit shipped the declaration with
nothing consuming it). Generated before register(), so a plugin declaring both gets
its generated page first, then its own; the page id is fixed so a plugin's own
registerSettingsPage cannot shadow it.
PluginSettingsPage wires the concurrency story end to end: the draft carries a
revision, every save sends it back as expectedRevision, and a stale save is rejected
by the server rather than silently clobbering another tab. The server's message is
surfaced verbatim because "does not match the schema" and "changed elsewhere" need
different actions from the user.
The form opens on incompatible stored data and says so, rather than refusing — that
is exactly when the user needs it to repair the values, and the stored data is
preserved until a valid save (Sol spec review MUST pingdotgg#13's recovery half).
This also makes the previously-vacuous test assertable. It now asserts the generated
page appears, and is verified deletion-sensitive: removing the synthesis fails both
new tests. `declared: false` (no schema, or plugin disabled) renders an explanation
rather than an empty form.
Gates: typecheck 0 errors; web 1319 passed; server 1672 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 16, 2026
Sol spec review MUST pingdotgg#13 (detection half). The store recorded a schema fingerprint
from the first commit but nothing ever read it back, so an upgrade that changed a
plugin's schema under already-stored values went undetected — the values were read
as if they still matched, which is exactly the silent misread the fingerprint exists
to prevent.
readDraft now surfaces the fingerprint that produced the stored values, and the
settings RPC compares it against the plugin's CURRENT schema. A mismatch marks the
draft incompatible, which the settings page already renders as "these settings need
attention" with the form open for repair.
The stored data is preserved on mismatch, not discarded: the user may need to read
the old values to reconstruct the new ones, and a plugin that briefly downgrades
should not lose its config. Recovery is a valid save, which rewrites both the values
and the fingerprint.
Tests verified deletion-sensitive: returning a null fingerprint from readDraft fails
both fingerprint tests.
Remaining for MUST pingdotgg#13: hostApi.settings.get should fail InvalidStored on drift
rather than relying on decode alone. The decode already fails for an incompatible
shape in the common case; the fingerprint makes it explicit. Tracked, not claimed.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 16, 2026
Sol spec review MUST pingdotgg#13, plugin-read half. hostApi.settings.get now rejects stored
values whose fingerprint does not match the plugin's current schema, instead of
relying on decode to notice.
Decode is not sufficient and that is the whole point: a widening change (adding an
optional field, relaxing a filter) still decodes cleanly, so the plugin would
silently run on values written for a shape it no longer declares. Comparing the
fingerprint makes the mismatch the fact being checked rather than a side effect of
decoding. The error carries no values — only the plugin id.
KNOWN GAP, stated rather than implied: this path has no test. I wrote one with a
fixture that probes settings.get from a service and records the Exit tag to its data
dir; the probe did not get scheduled within the test's wait, and the "<probe never
ran>" guard I built into it caught that rather than letting it pass vacuously against
whatever I asserted. Rather than ship a test that cannot reliably observe the
behaviour — or one that asserts only "the plugin activated", which would pass with
this check deleted — the tests are removed and the gap recorded.
Store-level fingerprint behaviour IS covered and verified deletion-sensitive
(PluginSettingsStore.test.ts); what is untested is specifically the capability read
path rejecting on drift. Needs a fixture harness that can await a forked service.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 16, 2026
Makes declarative settings real: a plugin declares a schema and the HOST renders the
form. The plugin ships no form, no RPC, no storage.
The loader now synthesises a settings page from `definition.settings`, so the
declaration is no longer inert (the previous commit shipped the declaration with
nothing consuming it). Generated before register(), so a plugin declaring both gets
its generated page first, then its own; the page id is fixed so a plugin's own
registerSettingsPage cannot shadow it.
PluginSettingsPage wires the concurrency story end to end: the draft carries a
revision, every save sends it back as expectedRevision, and a stale save is rejected
by the server rather than silently clobbering another tab. The server's message is
surfaced verbatim because "does not match the schema" and "changed elsewhere" need
different actions from the user.
The form opens on incompatible stored data and says so, rather than refusing — that
is exactly when the user needs it to repair the values, and the stored data is
preserved until a valid save (Sol spec review MUST pingdotgg#13's recovery half).
This also makes the previously-vacuous test assertable. It now asserts the generated
page appears, and is verified deletion-sensitive: removing the synthesis fails both
new tests. `declared: false` (no schema, or plugin disabled) renders an explanation
rather than an empty form.
Gates: typecheck 0 errors; web 1319 passed; server 1672 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 16, 2026
Sol spec review MUST pingdotgg#13 (detection half). The store recorded a schema fingerprint
from the first commit but nothing ever read it back, so an upgrade that changed a
plugin's schema under already-stored values went undetected — the values were read
as if they still matched, which is exactly the silent misread the fingerprint exists
to prevent.
readDraft now surfaces the fingerprint that produced the stored values, and the
settings RPC compares it against the plugin's CURRENT schema. A mismatch marks the
draft incompatible, which the settings page already renders as "these settings need
attention" with the form open for repair.
The stored data is preserved on mismatch, not discarded: the user may need to read
the old values to reconstruct the new ones, and a plugin that briefly downgrades
should not lose its config. Recovery is a valid save, which rewrites both the values
and the fingerprint.
Tests verified deletion-sensitive: returning a null fingerprint from readDraft fails
both fingerprint tests.
Remaining for MUST pingdotgg#13: hostApi.settings.get should fail InvalidStored on drift
rather than relying on decode alone. The decode already fails for an incompatible
shape in the common case; the fingerprint makes it explicit. Tracked, not claimed.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 16, 2026
Sol spec review MUST pingdotgg#13, plugin-read half. hostApi.settings.get now rejects stored
values whose fingerprint does not match the plugin's current schema, instead of
relying on decode to notice.
Decode is not sufficient and that is the whole point: a widening change (adding an
optional field, relaxing a filter) still decodes cleanly, so the plugin would
silently run on values written for a shape it no longer declares. Comparing the
fingerprint makes the mismatch the fact being checked rather than a side effect of
decoding. The error carries no values — only the plugin id.
KNOWN GAP, stated rather than implied: this path has no test. I wrote one with a
fixture that probes settings.get from a service and records the Exit tag to its data
dir; the probe did not get scheduled within the test's wait, and the "<probe never
ran>" guard I built into it caught that rather than letting it pass vacuously against
whatever I asserted. Rather than ship a test that cannot reliably observe the
behaviour — or one that asserts only "the plugin activated", which would pass with
this check deleted — the tests are removed and the gap recorded.
Store-level fingerprint behaviour IS covered and verified deletion-sensitive
(PluginSettingsStore.test.ts); what is untested is specifically the capability read
path rejecting on drift. Needs a fixture harness that can await a forked service.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 17, 2026
Makes declarative settings real: a plugin declares a schema and the HOST renders the
form. The plugin ships no form, no RPC, no storage.
The loader now synthesises a settings page from `definition.settings`, so the
declaration is no longer inert (the previous commit shipped the declaration with
nothing consuming it). Generated before register(), so a plugin declaring both gets
its generated page first, then its own; the page id is fixed so a plugin's own
registerSettingsPage cannot shadow it.
PluginSettingsPage wires the concurrency story end to end: the draft carries a
revision, every save sends it back as expectedRevision, and a stale save is rejected
by the server rather than silently clobbering another tab. The server's message is
surfaced verbatim because "does not match the schema" and "changed elsewhere" need
different actions from the user.
The form opens on incompatible stored data and says so, rather than refusing — that
is exactly when the user needs it to repair the values, and the stored data is
preserved until a valid save (Sol spec review MUST pingdotgg#13's recovery half).
This also makes the previously-vacuous test assertable. It now asserts the generated
page appears, and is verified deletion-sensitive: removing the synthesis fails both
new tests. `declared: false` (no schema, or plugin disabled) renders an explanation
rather than an empty form.
Gates: typecheck 0 errors; web 1319 passed; server 1672 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 17, 2026
Sol spec review MUST pingdotgg#13 (detection half). The store recorded a schema fingerprint
from the first commit but nothing ever read it back, so an upgrade that changed a
plugin's schema under already-stored values went undetected — the values were read
as if they still matched, which is exactly the silent misread the fingerprint exists
to prevent.
readDraft now surfaces the fingerprint that produced the stored values, and the
settings RPC compares it against the plugin's CURRENT schema. A mismatch marks the
draft incompatible, which the settings page already renders as "these settings need
attention" with the form open for repair.
The stored data is preserved on mismatch, not discarded: the user may need to read
the old values to reconstruct the new ones, and a plugin that briefly downgrades
should not lose its config. Recovery is a valid save, which rewrites both the values
and the fingerprint.
Tests verified deletion-sensitive: returning a null fingerprint from readDraft fails
both fingerprint tests.
Remaining for MUST pingdotgg#13: hostApi.settings.get should fail InvalidStored on drift
rather than relying on decode alone. The decode already fails for an incompatible
shape in the common case; the fingerprint makes it explicit. Tracked, not claimed.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 17, 2026
Sol spec review MUST pingdotgg#13, plugin-read half. hostApi.settings.get now rejects stored
values whose fingerprint does not match the plugin's current schema, instead of
relying on decode to notice.
Decode is not sufficient and that is the whole point: a widening change (adding an
optional field, relaxing a filter) still decodes cleanly, so the plugin would
silently run on values written for a shape it no longer declares. Comparing the
fingerprint makes the mismatch the fact being checked rather than a side effect of
decoding. The error carries no values — only the plugin id.
KNOWN GAP, stated rather than implied: this path has no test. I wrote one with a
fixture that probes settings.get from a service and records the Exit tag to its data
dir; the probe did not get scheduled within the test's wait, and the "<probe never
ran>" guard I built into it caught that rather than letting it pass vacuously against
whatever I asserted. Rather than ship a test that cannot reliably observe the
behaviour — or one that asserts only "the plugin activated", which would pass with
this check deleted — the tests are removed and the gap recorded.
Store-level fingerprint behaviour IS covered and verified deletion-sensitive
(PluginSettingsStore.test.ts); what is untested is specifically the capability read
path rejecting on drift. Needs a fixture harness that can await a forked service.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 30, 2026
Makes declarative settings real: a plugin declares a schema and the HOST renders the
form. The plugin ships no form, no RPC, no storage.
The loader now synthesises a settings page from `definition.settings`, so the
declaration is no longer inert (the previous commit shipped the declaration with
nothing consuming it). Generated before register(), so a plugin declaring both gets
its generated page first, then its own; the page id is fixed so a plugin's own
registerSettingsPage cannot shadow it.
PluginSettingsPage wires the concurrency story end to end: the draft carries a
revision, every save sends it back as expectedRevision, and a stale save is rejected
by the server rather than silently clobbering another tab. The server's message is
surfaced verbatim because "does not match the schema" and "changed elsewhere" need
different actions from the user.
The form opens on incompatible stored data and says so, rather than refusing — that
is exactly when the user needs it to repair the values, and the stored data is
preserved until a valid save (Sol spec review MUST pingdotgg#13's recovery half).
This also makes the previously-vacuous test assertable. It now asserts the generated
page appears, and is verified deletion-sensitive: removing the synthesis fails both
new tests. `declared: false` (no schema, or plugin disabled) renders an explanation
rather than an empty form.
Gates: typecheck 0 errors; web 1319 passed; server 1672 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 30, 2026
Sol spec review MUST pingdotgg#13 (detection half). The store recorded a schema fingerprint
from the first commit but nothing ever read it back, so an upgrade that changed a
plugin's schema under already-stored values went undetected — the values were read
as if they still matched, which is exactly the silent misread the fingerprint exists
to prevent.
readDraft now surfaces the fingerprint that produced the stored values, and the
settings RPC compares it against the plugin's CURRENT schema. A mismatch marks the
draft incompatible, which the settings page already renders as "these settings need
attention" with the form open for repair.
The stored data is preserved on mismatch, not discarded: the user may need to read
the old values to reconstruct the new ones, and a plugin that briefly downgrades
should not lose its config. Recovery is a valid save, which rewrites both the values
and the fingerprint.
Tests verified deletion-sensitive: returning a null fingerprint from readDraft fails
both fingerprint tests.
Remaining for MUST pingdotgg#13: hostApi.settings.get should fail InvalidStored on drift
rather than relying on decode alone. The decode already fails for an incompatible
shape in the common case; the fingerprint makes it explicit. Tracked, not claimed.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 30, 2026
Sol spec review MUST pingdotgg#13, plugin-read half. hostApi.settings.get now rejects stored
values whose fingerprint does not match the plugin's current schema, instead of
relying on decode to notice.
Decode is not sufficient and that is the whole point: a widening change (adding an
optional field, relaxing a filter) still decodes cleanly, so the plugin would
silently run on values written for a shape it no longer declares. Comparing the
fingerprint makes the mismatch the fact being checked rather than a side effect of
decoding. The error carries no values — only the plugin id.
KNOWN GAP, stated rather than implied: this path has no test. I wrote one with a
fixture that probes settings.get from a service and records the Exit tag to its data
dir; the probe did not get scheduled within the test's wait, and the "<probe never
ran>" guard I built into it caught that rather than letting it pass vacuously against
whatever I asserted. Rather than ship a test that cannot reliably observe the
behaviour — or one that asserts only "the plugin activated", which would pass with
this check deleted — the tests are removed and the gap recorded.
Store-level fingerprint behaviour IS covered and verified deletion-sensitive
(PluginSettingsStore.test.ts); what is untested is specifically the capability read
path rejecting on drift. Needs a fixture harness that can await a forked service.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 30, 2026
Makes declarative settings real: a plugin declares a schema and the HOST renders the
form. The plugin ships no form, no RPC, no storage.
The loader now synthesises a settings page from `definition.settings`, so the
declaration is no longer inert (the previous commit shipped the declaration with
nothing consuming it). Generated before register(), so a plugin declaring both gets
its generated page first, then its own; the page id is fixed so a plugin's own
registerSettingsPage cannot shadow it.
PluginSettingsPage wires the concurrency story end to end: the draft carries a
revision, every save sends it back as expectedRevision, and a stale save is rejected
by the server rather than silently clobbering another tab. The server's message is
surfaced verbatim because "does not match the schema" and "changed elsewhere" need
different actions from the user.
The form opens on incompatible stored data and says so, rather than refusing — that
is exactly when the user needs it to repair the values, and the stored data is
preserved until a valid save (Sol spec review MUST pingdotgg#13's recovery half).
This also makes the previously-vacuous test assertable. It now asserts the generated
page appears, and is verified deletion-sensitive: removing the synthesis fails both
new tests. `declared: false` (no schema, or plugin disabled) renders an explanation
rather than an empty form.
Gates: typecheck 0 errors; web 1319 passed; server 1672 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 30, 2026
Sol spec review MUST pingdotgg#13 (detection half). The store recorded a schema fingerprint
from the first commit but nothing ever read it back, so an upgrade that changed a
plugin's schema under already-stored values went undetected — the values were read
as if they still matched, which is exactly the silent misread the fingerprint exists
to prevent.
readDraft now surfaces the fingerprint that produced the stored values, and the
settings RPC compares it against the plugin's CURRENT schema. A mismatch marks the
draft incompatible, which the settings page already renders as "these settings need
attention" with the form open for repair.
The stored data is preserved on mismatch, not discarded: the user may need to read
the old values to reconstruct the new ones, and a plugin that briefly downgrades
should not lose its config. Recovery is a valid save, which rewrites both the values
and the fingerprint.
Tests verified deletion-sensitive: returning a null fingerprint from readDraft fails
both fingerprint tests.
Remaining for MUST pingdotgg#13: hostApi.settings.get should fail InvalidStored on drift
rather than relying on decode alone. The decode already fails for an incompatible
shape in the common case; the fingerprint makes it explicit. Tracked, not claimed.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 30, 2026
Sol spec review MUST pingdotgg#13, plugin-read half. hostApi.settings.get now rejects stored
values whose fingerprint does not match the plugin's current schema, instead of
relying on decode to notice.
Decode is not sufficient and that is the whole point: a widening change (adding an
optional field, relaxing a filter) still decodes cleanly, so the plugin would
silently run on values written for a shape it no longer declares. Comparing the
fingerprint makes the mismatch the fact being checked rather than a side effect of
decoding. The error carries no values — only the plugin id.
KNOWN GAP, stated rather than implied: this path has no test. I wrote one with a
fixture that probes settings.get from a service and records the Exit tag to its data
dir; the probe did not get scheduled within the test's wait, and the "<probe never
ran>" guard I built into it caught that rather than letting it pass vacuously against
whatever I asserted. Rather than ship a test that cannot reliably observe the
behaviour — or one that asserts only "the plugin activated", which would pass with
this check deleted — the tests are removed and the gap recorded.
Store-level fingerprint behaviour IS covered and verified deletion-sensitive
(PluginSettingsStore.test.ts); what is untested is specifically the capability read
path rejecting on drift. Needs a fixture harness that can await a forked service.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 31, 2026
Makes declarative settings real: a plugin declares a schema and the HOST renders the
form. The plugin ships no form, no RPC, no storage.
The loader now synthesises a settings page from `definition.settings`, so the
declaration is no longer inert (the previous commit shipped the declaration with
nothing consuming it). Generated before register(), so a plugin declaring both gets
its generated page first, then its own; the page id is fixed so a plugin's own
registerSettingsPage cannot shadow it.
PluginSettingsPage wires the concurrency story end to end: the draft carries a
revision, every save sends it back as expectedRevision, and a stale save is rejected
by the server rather than silently clobbering another tab. The server's message is
surfaced verbatim because "does not match the schema" and "changed elsewhere" need
different actions from the user.
The form opens on incompatible stored data and says so, rather than refusing — that
is exactly when the user needs it to repair the values, and the stored data is
preserved until a valid save (Sol spec review MUST pingdotgg#13's recovery half).
This also makes the previously-vacuous test assertable. It now asserts the generated
page appears, and is verified deletion-sensitive: removing the synthesis fails both
new tests. `declared: false` (no schema, or plugin disabled) renders an explanation
rather than an empty form.
Gates: typecheck 0 errors; web 1319 passed; server 1672 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 31, 2026
Sol spec review MUST pingdotgg#13 (detection half). The store recorded a schema fingerprint
from the first commit but nothing ever read it back, so an upgrade that changed a
plugin's schema under already-stored values went undetected — the values were read
as if they still matched, which is exactly the silent misread the fingerprint exists
to prevent.
readDraft now surfaces the fingerprint that produced the stored values, and the
settings RPC compares it against the plugin's CURRENT schema. A mismatch marks the
draft incompatible, which the settings page already renders as "these settings need
attention" with the form open for repair.
The stored data is preserved on mismatch, not discarded: the user may need to read
the old values to reconstruct the new ones, and a plugin that briefly downgrades
should not lose its config. Recovery is a valid save, which rewrites both the values
and the fingerprint.
Tests verified deletion-sensitive: returning a null fingerprint from readDraft fails
both fingerprint tests.
Remaining for MUST pingdotgg#13: hostApi.settings.get should fail InvalidStored on drift
rather than relying on decode alone. The decode already fails for an incompatible
shape in the common case; the fingerprint makes it explicit. Tracked, not claimed.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 31, 2026
Sol spec review MUST pingdotgg#13, plugin-read half. hostApi.settings.get now rejects stored
values whose fingerprint does not match the plugin's current schema, instead of
relying on decode to notice.
Decode is not sufficient and that is the whole point: a widening change (adding an
optional field, relaxing a filter) still decodes cleanly, so the plugin would
silently run on values written for a shape it no longer declares. Comparing the
fingerprint makes the mismatch the fact being checked rather than a side effect of
decoding. The error carries no values — only the plugin id.
KNOWN GAP, stated rather than implied: this path has no test. I wrote one with a
fixture that probes settings.get from a service and records the Exit tag to its data
dir; the probe did not get scheduled within the test's wait, and the "<probe never
ran>" guard I built into it caught that rather than letting it pass vacuously against
whatever I asserted. Rather than ship a test that cannot reliably observe the
behaviour — or one that asserts only "the plugin activated", which would pass with
this check deleted — the tests are removed and the gap recorded.
Store-level fingerprint behaviour IS covered and verified deletion-sensitive
(PluginSettingsStore.test.ts); what is untested is specifically the capability read
path rejecting on drift. Needs a fixture harness that can await a forked service.
Gates: typecheck 0 errors; server 1675 passed.
aorwall added a commit to aorwall/t3code that referenced this pull request Aug 2, 2026
* docs: spec for preview servers in the T3 Code web app
Approved spec covering the Servers right-panel view, Moatless-owned preview
tabs backed by Redis, and an iframe renderer that lets the existing browser
panel work outside Electron.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat: add the servers.* contract group
Three reads over the servers a thread's environment declares: list them, watch
their status, and follow one's log. Field names are taken verbatim from the
host's own server record so the server side is a rename-free serialization.
Nothing here starts, stops or reconfigures a server; those methods are
specified elsewhere and deliberately out of scope.
The preview module docstring said the preview is desktop-only and that the
desktop renderer mediates. Both stop being true in the same change, so the
docstring now describes the surface by capability rather than by client.
* feat: carry the servers.* group through the client runtime and reference server
Client-side atoms for the three reads, with the log subscription folding
lines into a bounded buffer so a remounted panel keeps what it received
rather than starting over.
The reference server declares no thread servers - it runs threads on the
machine it is on - so it answers the list empty and both subscriptions
silent. A hosted environment answers the same three methods with real data.
Also splits the preview runtime capability in two. One boolean was answering
both "can this runtime show a page" and "can this runtime read the page it
shows", which are the same question only on the desktop app.
* feat: host preview pages in a sandboxed frame on the web
The web app could not show a task's page at all. It can now: one frame per
open tab, mounted at the app root so it survives the panel collapsing, and
positioned by the same surface rect the desktop webview uses.
The frame is driven and never read. Navigation writes to the server and the
frame follows; refresh replaces the element because a cross-origin frame has
no reload and reassigning src would grow the parent's history; back and
forward are absent rather than dead.
Two things a frame cannot report, the panel says from elsewhere. A page that
is not there is explained by what its server says about itself, which is
better than a net error - "installing" is an answer no net error carries. A
page that renders nothing while its server says it is running gets a hint
offered as a hint, because a frame-ancestors refusal and the preview host's
own 401 look identical from outside and neither fires an event.
* feat: add a Servers view to the right panel
One row per server the thread's environment declares, with its status kept
current by the subscription, its log on demand, and an Open that hands the
URL to a browser tab.
Everything in it is a read - a row that says failed offers its log and no
button. Restarting a server is a write and lands elsewhere.
The right panel's persisted state moves to version 8 for the new surface
kind, and its migration now drops surfaces whose kind this build does not
know. That is what makes the version bump safe to downgrade away from: the
rest of the workspace survives and only the unknown tab is lost.
* test: cover the browser preview surface and the servers view
Two products share these schemas and no test process, so the seam is a set
of real Moatless responses checked in here and decoded by the schemas
themselves. When its projection changes the fixture changes with it in one
commit, and the decode test is what fails if the two drift.
The rest covers what the change actually promises: the capability answers
three runtimes, the chrome row omits controls rather than disabling them,
the frame re-keys instead of reassigning src, exactly one browser host
renders, and the panel state survives the version bump while dropping a kind
this build does not know.
* fix: state the environment's absence rather than implying it from a row
The never-provisioned fixture claimed a server is listed as `stopped` with
no URL. It is not. Moatless resolves status config-first from a NotFound
pod, which falls back to `starting` with the ingress URL the port will have
— so the panel showed `starting` forever for an environment that does not
exist, and offered an Open button pointing at a 502.
The fixture now carries what the backend produces, and the panel states the
environment's own status above the list instead of leaving it to be inferred
from rows that cannot say it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: track the fork's own delta, not just what upstream does to it
The policy could answer "who wins this conflict" but not "what did we change",
and the second is the question you are actually asking when git stops on a line
you do not recognise. The path table only ever listed files we expected to fight
over — a much smaller set than the files we changed. The whole thread-servers
group, around forty files, appeared nowhere.
§3 is that missing list, and a fifth hazard in the preamble names the failure it
prevents: a conflict resolved toward upstream because nobody could tell our line
was deliberate. Two consequences: the checklist asks for a row in the same commit
as the change rather than at the next merge, and reading the inventory is now a
step in resolving a conflict rather than something to remember.
Also adds path-policy rows for the highest-risk of those files —
`apps/server/src/ws.ts` and `RpcAuthorization.ts`, the only upstream server files
the fork touches, where the resolution is to take theirs and re-add three
`servers.*` entries.
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
aorwall pushed a commit to aorwall/t3code that referenced this pull request Aug 12, 2026
The 2026-08-08 upstream merge took 89 minutes. Most of that was not conflict
resolution; it was performing checks that existed only as prose in
docs/fork/upstream-merge-inventory.md, one at a time, and rediscovering two
things the doc could have told me before I started.
Five changes, in the order they pay off.
**The inventory is data now.** docs/fork/inventory.json holds the fork-owned
concerns, path policy, fork inventory, tripwires, deliberately deleted upstream
paths, off-repository state and convergence entries. The markdown keeps only
what a check cannot hold — the four re-application deltas, where the
unsupported-method set comes from, and the reasoning behind the path policy
rule — and drops from 784 lines to 243.
**The checks run.** Five dependency-free scripts under the skill:
- `preflight.mjs` — the range, stale entries, the owned-concern sweep, and the
conflict forecast: every file both sides touched, grouped by the verdict that
resolves it, before the merge starts.
- `inventory-check.mjs` — every inventory path read back out of upstream/main.
This is the one that matters: last merge, upstream moved SidebarV2's content
into Sidebar.tsx and Sidebar.tsx's into LegacySidebar.tsx. Git cannot see a
content swap as a rename — it is a delete paired with a modify, which no -M
threshold detects — so it surfaced as a modify/delete conflict mid-merge.
Run against the pre-merge tree, this check names it in about a second.
- `tripwires.mjs`, `unsupported-methods.mjs`, `verify.mjs`.
Running inventory-check against the current tree immediately found three dead
path-policy entries that had been faithfully transcribed forward through
several merges, for directories deleted in pingdotgg#13 and pingdotgg#26.
**Verification is one command, earlier.** `verify.mjs` runs tripwires, the
unsupported-method derivation, format, lint, types and tests — and does not
stop at the first failure, so a formatting nit no longer hides the type errors
behind it. It raises the heap the web suite needs, whose failure mode is
otherwise an exit 137 that reads like a real test failure. The skill now runs
it *before* the documentation steps, because its output is their input.
**Counts come out of prose.** Both snapshots the last merge relied on were
stale. "38 of 87 methods" and "Clerk 4 / pairing 73 / session bootstrap 9" are
replaced by the command that derives them.
**The fork test no longer hard-codes upstream paths.** features.test.ts read
`../components/Sidebar.tsx?raw`; when upstream renamed that file the whole
suite failed to build, at a module path, saying nothing about the gate. It now
reads the guarded files out of inventory.json and reaches them through
import.meta.glob, so a rename fails as a named assertion that says which entry
to re-point — and the test and the merge scripts read the same guards, so they
cannot drift apart.
Verified: fmt, lint, typecheck and 1908 tests pass. `tripwires.mjs` reports one
finding, which is genuine and already tracked — thread-transfer-report.yml went
active on GitHub when the merge branch was pushed and still needs disabling.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
NeilTheFisher pushed a commit to NeilTheFisher/t3code that referenced this pull request Aug 18, 2026
AFLabAI added a commit to AFLabAI/t3code that referenced this pull request Sep 2, 2026
Increase diagnostic visibility by appending complete vp check stdout/stderr to job summary instead of truncating. This exposes all 11 lint errors detected in RUN pingdotgg#13.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Sign up for freeto 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

@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat: Commit, Push and Create PR actions - #13

Merged
juliusmarminge merged 18 commits into
mainfrom
codex/add-github-commit-push-ui
Feb 12, 2026
Merged

feat: Commit, Push and Create PR actions#13
juliusmarminge merged 18 commits into
mainfrom
codex/add-github-commit-push-ui

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Feb 12, 2026

Copy link
Copy Markdown
Member

Open with Devin

Summary by CodeRabbit

  • New Features

    • AI-powered generation for commit messages and PR title/body.
    • New Git core & manager with stacked workflows (commit, commit & push, commit & push & create/open PR), richer status details, and WebSocket/native endpoints to run Git status and actions.
    • UI: Git actions menu with real-time status, action execution, notices and error handling.
  • Chores

    • Robust process runner with buffer and timeout safeguards.
  • Tests

    • Extensive end-to-end and unit tests covering Git flows, PR lifecycle, manager behavior, and contract schemas.

@coderabbitai

coderabbitaiBot commented Feb 12, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds a Git subsystem: process runner with buffer/timeouts, Git core service, GitManager orchestration, Codex-based commit/PR generation, WS API + server wiring, client UI integration, contracts/schemas for stacked git actions, and extensive tests.

Changes

Cohort / File(s)Summary
Text Generation Service
apps/server/src/coreServices.ts, apps/server/src/codexTextGenerator.ts
Defines TextGenerationService types and implements CodexTextGenerator with JSON schemas, validation/parsers, temp-file helpers, runCodexJson orchestration, and methods to generate commit messages and PR content.
Process Execution
apps/server/src/processRunner.ts
Adds runProcess with configurable maxBufferBytes, stdout/stderr accumulation and byte limits, timeout handling (SIGTERM→SIGKILL), spawn/exit normalization, and detailed error types.
Git Core Service
apps/server/src/git.ts
Introduces GitCoreService and helpers: enriched status/statusDetails, prepareCommitContext, commit, pushCurrentBranch, readRangeContext, readConfigValue, runGit helpers, and upstream/default-branch logic.
GitManager Orchestration
apps/server/src/gitManager.ts
Adds GitManager coordinating gitCore, processRunner, Codex text generation, and gh interactions for stacked actions (commit, push, PR), PR discovery/creation, temp-file PR bodies, and error normalization.
Server WS Integration & Tests
apps/server/src/wsServer.ts, apps/server/src/wsServer.test.ts
Wires GitManager into server options, exposes WS methods git.status and git.runStackedAction, and updates tests to inject/validate gitManager delegation and error propagation.
Tests: GitManager & GitCore
apps/server/src/gitManager.test.ts, apps/server/src/git.test.ts
Adds end-to-end tests covering stacked actions, GH CLI scenarios, upstream behavior, detached HEAD, auth errors, and GitCoreService behaviors.
Client Integration
apps/web/src/wsNativeApi.ts, apps/web/src/components/ChatView.tsx
Extends native API with git.status and git.runStackedAction; integrates Git actions menu and UI state into ChatView with status polling, action execution, and notices.
Contracts / IPC / WS Methods & Tests
packages/contracts/src/git.ts, packages/contracts/src/git.test.ts, packages/contracts/src/ipc.ts, packages/contracts/src/ws.ts
Adds Zod schemas/types for git status and stacked actions, updates NativeApi.git signatures, expands WS_METHODS with git.status and git.runStackedAction, and adds schema tests.

Sequence Diagram(s)

sequenceDiagram
participant Client as Client (ChatView)
participant WS as WebSocket Server
participant GM as GitManager
participant GC as GitCoreService
participant Proc as ProcessRunner
participant Codex as Codex Service
participant GH as GitHub CLI
Client->>WS: git.runStackedAction(action, cwd)
WS->>GM: runStackedAction()
rect rgba(100,150,200,0.5)
Note over GM: Commit Step
GM->>GC: prepareCommitContext(cwd)
GC-->>GM: stagedSummary, stagedPatch
GM->>Codex: generateCommitMessage(diff)
Codex-->>GM: subject, body
GM->>Proc: git commit -m "..."
Proc-->>GM: commit result
end
rect rgba(100,200,150,0.5)
Note over GM: Push Step (if requested)
GM->>GC: pushCurrentBranch(cwd, upstream?)
GC-->>GM: push result
end
rect rgba(200,150,100,0.5)
Note over GM: PR Step (if requested)
GM->>GC: readRangeContext(base, head)
GC-->>GM: commitSummary, diffSummary, diffPatch
GM->>Codex: generatePrContent(rangeContext)
Codex-->>GM: title, body
GM->>GH: gh pr list --head branch
GH-->>GM: existing PRs
alt PR exists
GM->>GH: gh pr view PR_NUMBER
GH-->>GM: PR details
else
GM->>GH: gh pr create --title "..." --body file://tmp
GH-->>GM: new PR info
end
end
GM-->>WS: GitRunStackedActionResult
WS-->>Client: result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 2.56% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title 'feat: Commit, Push and Create PR actions' accurately captures the main feature added: three new Git workflow actions integrated into the UI and backend services.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch codex/add-github-commit-push-ui

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps

greptile-appsBot commented Feb 12, 2026

Copy link
Copy Markdown

Greptile Overview

Greptile Summary

This PR implements comprehensive Git stacked actions (commit, push, create PR) with AI-powered text generation for commit messages and PR content. The implementation spans server-side orchestration, robust process execution, WebSocket/IPC integration, and a polished React UI with real-time progress tracking.

Key changes:

  • New GitManager orchestrates stacked workflows (commit → push → PR) with proper error handling and GitHub CLI integration
  • GitCoreService provides low-level Git operations using direct spawn (no shell), with buffer management and timeout safeguards
  • CodexTextGenerator generates commit messages and PR content via Codex CLI with structured JSON output schemas
  • ProcessRunner implements robust subprocess execution with truncation support, graceful termination, and buffer limit enforcement
  • GitActionsControl UI component provides modal-based workflow with step-by-step progress, custom commit messages, and error states
  • Comprehensive test coverage across unit tests (git.test.ts, processRunner.test.ts) and integration tests (gitManager.test.ts)
  • WebSocket and IPC layers extended to expose git.status and git.runStackedAction methods to both web and desktop clients

Architecture aligns with project priorities:

  • Performance: Direct spawn without shell overhead, buffer limits prevent memory issues
  • Reliability: Proper timeout handling, graceful degradation (PR lookup is best-effort), temp file cleanup
  • Predictable behavior: Structured status tracking, deterministic base branch resolution, comprehensive error normalization

Confidence Score: 5/5

  • This PR is safe to merge with high confidence
  • The implementation demonstrates strong engineering discipline: comprehensive test coverage (490 lines in gitManager.test.ts alone), proper error handling with normalized error messages, robust subprocess management with buffer limits and timeouts, dependency injection for testability, and alignment with project priorities (performance, reliability, predictable behavior). The only noted issue is duplicate commit message sanitization which is cosmetic and doesn't affect functionality.
  • No files require special attention

Important Files Changed

FilenameOverview
apps/server/src/git.tsAdds comprehensive Git operations with robust error handling, proper spawn arguments, and buffer management for status, commit, push, and range context operations
apps/server/src/gitManager.tsImplements high-level orchestration for stacked Git workflows (commit, push, PR) with AI text generation, proper dependency injection, and GitHub CLI integration
apps/server/src/codexTextGenerator.tsImplements AI-powered text generation for commit messages and PR content using Codex CLI with structured JSON output, proper sanitization, and temp file cleanup
apps/server/src/processRunner.tsAdds robust process execution with buffer limit enforcement, timeout handling, graceful termination (SIGTERM then SIGKILL), and truncation support
apps/server/src/wsServer.tsIntegrates GitManager into WebSocket server to expose git.status and git.runStackedAction methods via WS protocol
packages/contracts/src/git.tsExtends Git contracts with Zod schemas for stacked action API (status, commit, push, PR) with comprehensive validation rules
apps/web/src/components/GitActionsControl.tsxImplements comprehensive Git actions UI with modal workflow, real-time progress tracking, step-by-step execution, error handling, and PR link opening

Sequence Diagram

sequenceDiagram
participant User
participant GitActionsControl
participant NativeApi
participant GitManager
participant GitCore
participant CodexTextGenerator
participant GitCLI
participant GitHubCLI
User->>GitActionsControl: Click "Commit and create PR"
GitActionsControl->>GitActionsControl: Open modal, set action
User->>GitActionsControl: Confirm action
GitActionsControl->>NativeApi: git.runStackedAction(commit)
NativeApi->>GitManager: runStackedAction(commit)
GitManager->>GitCore: statusDetails(cwd)
GitCore->>GitCLI: git status --porcelain=2
GitCLI-->>GitCore: status output
GitCore-->>GitManager: branch, upstream info
GitManager->>GitCore: prepareCommitContext(cwd)
GitCore->>GitCLI: git add -A
GitCore->>GitCLI: git diff --cached
GitCLI-->>GitCore: staged changes
GitCore-->>GitManager: stagedSummary, stagedPatch
GitManager->>CodexTextGenerator: generateCommitMessage()
CodexTextGenerator->>CodexTextGenerator: Write temp schema file
CodexTextGenerator->>GitCLI: codex exec --output-schema
GitCLI-->>CodexTextGenerator: JSON response
CodexTextGenerator->>CodexTextGenerator: Parse, sanitize, cleanup
CodexTextGenerator-->>GitManager: {subject, body}
GitManager->>GitCore: commit(cwd, subject, body)
GitCore->>GitCLI: git commit -m subject -m body
GitCLI-->>GitCore: success
GitCore-->>GitManager: {commitSha}
GitManager-->>NativeApi: commit result
NativeApi-->>GitActionsControl: Update progress: commit completed
GitActionsControl->>NativeApi: git.runStackedAction(commit_push)
NativeApi->>GitManager: runStackedAction(commit_push)
GitManager->>GitCore: pushCurrentBranch(cwd)
GitCore->>GitCLI: git push -u origin branch
GitCLI-->>GitCore: success
GitCore-->>GitManager: {status: pushed, branch}
GitManager-->>NativeApi: push result
NativeApi-->>GitActionsControl: Update progress: push completed
GitActionsControl->>NativeApi: git.runStackedAction(commit_push_pr)
NativeApi->>GitManager: runStackedAction(commit_push_pr)
GitManager->>GitHubCLI: gh pr list --head branch
GitHubCLI-->>GitManager: [] (no existing PR)
GitManager->>GitCore: readRangeContext(cwd, baseBranch)
GitCore->>GitCLI: git log, git diff
GitCLI-->>GitCore: commit history, diff
GitCore-->>GitManager: rangeContext
GitManager->>CodexTextGenerator: generatePrContent()
CodexTextGenerator->>GitCLI: codex exec --output-schema
GitCLI-->>CodexTextGenerator: JSON response
CodexTextGenerator-->>GitManager: {title, body}
GitManager->>GitHubCLI: gh pr create --title --body-file
GitHubCLI-->>GitManager: PR URL
GitManager->>GitHubCLI: gh pr view --web
GitHubCLI-->>GitManager: success
GitManager-->>NativeApi: pr result
NativeApi-->>GitActionsControl: Update progress: PR created
GitActionsControl->>User: Show completion with PR link
Loading

Last reviewed commit: dd92084

@greptile-appsgreptile-appsBot 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.

14 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment threadapps/server/src/gitManager.ts

@coderabbitaicoderabbitaiBot 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.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@apps/server/src/processRunner.ts`:
- Around line 128-131: Attach an 'error' listener to the child's stdin and use
the write callback to handle possible write errors before calling end: in
processRunner.ts, before calling child.stdin.write(...) register
child.stdin.once("error", err => {/* reject/forward error or cleanup and ensure
promise rejects */}), call child.stdin.write(options.stdin, (err) => { if (err)
{ /* handle/reject/cleanup */ } child.stdin.end(); }); and remove the
unconditional child.stdin.end() so we only end after the write completes; ensure
any error handling forwards the error to the same rejection/cleanup path used by
child.once("error") for the spawned process.
🧹 Nitpick comments (2)
packages/contracts/src/git.test.ts (1)

11-71: LGTM!

The tests provide good coverage for schema validation, including whitespace trimming and nested field parsing.

Consider adding negative test cases to verify that invalid inputs are rejected (e.g., invalid action strings, missing required fields). This would strengthen the contract validation.

,

apps/server/src/codexTextGenerator.ts (1)

84-103: Consider consolidating duplicate sanitization logic.

sanitizeCommitSubject here (lines 84-95) and sanitizeCommitMessage in gitManager.ts (lines 99-110) perform nearly identical operations: extracting the first line, removing trailing periods, and truncating to 72 characters. This duplication could lead to divergent behavior over time.

Consider extracting a shared utility or having gitManager.ts rely on the already-sanitized output from CodexTextGenerator without additional sanitization.

Comment threadapps/server/src/processRunner.ts
Comment threadapps/server/src/gitManager.ts Outdated
Comment threadapps/server/src/processRunner.ts
Comment threadapps/server/src/processRunner.ts
Comment threadapps/server/src/wsServer.ts
Comment threadapps/server/src/gitManager.ts Outdated
Comment threadapps/web/src/components/ChatView.tsx Outdated
Comment threadapps/server/src/codexTextGenerator.ts Outdated
@macroscopeapp

macroscopeappBot commented Feb 12, 2026

Copy link
Copy Markdown
Contributor

Add Commit, Push, and Create PR actions by introducing GitCoreService, GitManager.runStackedAction, and a web GitActionsControl wired through WS and IPC APIs

Implements stacked git workflows across server and web: adds GitCoreService for git operations, CodexTextGenerator for commit/PR text, and GitManager orchestration; exposes git.status and git.runStackedAction over WebSocket; adds desktop bridge shell.openExternal; and introduces a GitActionsControl UI to run commit/push/PR. Terminal spawning gains multi-shell fallback and runProcess provides standardized subprocess handling. See apps/server/src/gitManager.ts, apps/server/src/git.ts, apps/server/src/codexTextGenerator.ts, and apps/web/src/components/GitActionsControl.tsx.

📍Where to Start

Start with the orchestration entrypoint GitManager.runStackedAction in apps/server/src/gitManager.ts, then review GitCoreService in apps/server/src/git.ts and the web client GitActionsControl in apps/web/src/components/GitActionsControl.tsx.


Macroscope summarized dd92084.

Co-authored-by: codex <codex@users.noreply.github.com>
Comment threadapps/server/src/gitManager.ts

@coderabbitaicoderabbitaiBot 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.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@apps/web/src/components/ChatView.tsx`:
- Around line 395-424: When a git status fetch succeeds we need to clear any
previous error so the error banner doesn't persist; inside the load async
function (the one that calls api.git.status with gitCwd) after successfully
calling setGitStatus(nextStatus) also call setGitActionError(null) (guarded by
the same !cancelled check) so successful refreshes remove stale errors; update
the useEffect's load success branch in ChatView.tsx (the load function /
useEffect that references api, gitCwd, setGitStatus, setGitActionError)
accordingly.

Comment threadapps/web/src/components/ChatView.tsx Outdated
Comment threadapps/server/src/git.ts
Comment threadapps/server/src/git.ts Outdated

@coderabbitaicoderabbitaiBot 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.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@apps/server/src/git.test.ts`:
- Around line 460-466: The test invokes git with a single-quoted remote path
which fails on Windows; update the call that runs git(tmp.path, `remote add
origin '${remote.path}'`) to wrap the path using JSON.stringify(remote.path)
(i.e., produce a double-quoted, escaped string) so Windows cmd handles spaces
correctly; modify the test where makeTmpDir()/remote and the git(...) invocation
are used (see git(tmp.path, `remote add origin ...`), initRepoWithCommit,
createGitBranch) to pass the JSON.stringify-wrapped path instead of single
quotes.
In `@apps/server/src/git.ts`:
- Around line 245-251: The current early-return treats a branch as
"skipped_up_to_date" when details.hasUpstream && details.aheadCount === 0 even
if it is behind; update the condition to also require details.behindCount === 0
so we only mark truly up-to-date branches. Change the if that checks
details.hasUpstream and details.aheadCount to: details.hasUpstream &&
details.aheadCount === 0 && details.behindCount === 0 (leaving the returned
object with branch and optional upstreamBranch unchanged).
- Around line 129-143: The timeout check must be unconditional: in
runGitOrThrow, always throw when result.timedOut by calling
normalizeGitExecutionError(args, result) (or similar) before considering
options.allowNonZeroExit; then keep the existing non-zero exit handling for
result.code when options.allowNonZeroExit is false. Update the logic in
runGitOrThrow (referencing runGitOrThrow, RunGitOptions,
options.allowNonZeroExit, result.timedOut, result.code, and
normalizeGitExecutionError) so timeouts are detected and thrown unconditionally
while allowing suppressed non-zero exit codes only when appropriate.
In `@apps/server/src/gitManager.ts`:
- Around line 402-423: Both runGh and runGhStdout currently call this.run("gh",
args, { cwd }) and can hang; add an explicit timeoutMs option to those calls.
Define a clear constant (e.g. GH_CLI_TIMEOUT_MS = 30_000) near the top of the
module and pass it into this.run as { cwd, timeoutMs: GH_CLI_TIMEOUT_MS } in
both runGh and runGhStdout so gh CLI invocations time out predictably. Ensure
the constant is used in both functions and adjust any types if needed to match
ProcessRunOptions.
🧹 Nitpick comments (1)
apps/server/src/gitManager.ts (1)

175-183: If no commit is created, skip push/PR to avoid empty actions.

When runCommitStep returns skipped_no_changes, the current flow still pushes and can attempt PR creation. That can create confusing “no‑op” PRs or unnecessary network calls.

Comment threadapps/server/src/git.test.ts
Comment threadapps/server/src/git.ts
Comment threadapps/server/src/git.ts Outdated
Comment threadapps/server/src/gitManager.ts
@juliusmarmingejuliusmarminge changed the title Add stacked GitHub action workflowGithubFeb 12, 2026
@juliusmarmingejuliusmarminge changed the title Githubfeat: Commit, Push and Create PR actionsFeb 12, 2026
Co-authored-by: codex <codex@users.noreply.github.com>
Comment on lines +123 to +128
const fail = (error: Error): void => {
child.kill("SIGTERM");
finalize(() => {
reject(error);
});
};

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.

🟡 Medium

src/processRunner.ts:123 The fail function clears the timeout that would escalate to SIGKILL, so if the child ignores SIGTERM, the parent hangs forever. Consider adding a force-kill timer in fail similar to the timeout logic (lines 108-110).

- const fail = (error: Error): void => {- child.kill("SIGTERM");- finalize(() => {- reject(error);- });+ const fail = (error: Error): void => {+ child.kill("SIGTERM");+ const killTimer = setTimeout(() => {+ child.kill("SIGKILL");+ }, 1_000);+ finalize(() => {+ clearTimeout(killTimer);+ reject(error);+ });

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/processRunner.ts around lines 123-128:
The `fail` function clears the timeout that would escalate to `SIGKILL`, so if the child ignores `SIGTERM`, the parent hangs forever. Consider adding a force-kill timer in `fail` similar to the timeout logic (lines 108-110).

Comment threadapps/server/src/processRunner.ts Outdated
stream: "stdout" | "stderr",
chunk: Buffer | string,
): Error | null => {
const text = chunk.toString();

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.

🟢 Low

src/processRunner.ts:134 Using chunk.toString() per Buffer can split multi‑byte UTF‑8 and corrupt text; consider StringDecoder to assemble stdout/stderr safely.

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/processRunner.ts around line 134:
Using `chunk.toString()` per `Buffer` can split multi‑byte UTF‑8 and corrupt text; consider `StringDecoder` to assemble `stdout`/`stderr` safely.

@coderabbitaicoderabbitaiBot 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/server/src/git.ts (1)

50-86: ⚠️ Potential issue | 🟠 Major

Add output caps to avoid unbounded git command buffering.
runGit accumulates stdout/stderr without limits; large diffs can spike memory and make behavior unpredictable under load. Consider enforcing a max buffer (similar to processRunner) or streaming to disk with a safe cap.

🔧 Suggested fix (bounded output)
+const DEFAULT_GIT_MAX_BUFFER_BYTES = 8 * 1024 * 1024;+-function runGit(args: readonly string[], cwd: string, timeoutMs = 30_000): Promise<TerminalCommandResult> {+function runGit(+ args: readonly string[],+ cwd: string,+ timeoutMs = 30_000,+ maxBufferBytes = DEFAULT_GIT_MAX_BUFFER_BYTES,+): Promise<TerminalCommandResult> {
return new Promise((resolve, reject) => {
const child = spawn("git", args, {
cwd,
env: process.env,
stdio: ["ignore", "pipe", "pipe"],
});
let stdout = "";
let stderr = "";
let timedOut = false;
+ let stdoutBytes = 0;+ let stderrBytes = 0;+ let settled = false;
const timeout = setTimeout(() => {
timedOut = true;
child.kill("SIGTERM");
setTimeout(() => {
if (!child.killed) child.kill("SIGKILL");
}, 1_000).unref();
}, timeoutMs);
+ const fail = (error: Error) => {+ if (settled) return;+ settled = true;+ clearTimeout(timeout);+ child.kill("SIGTERM");+ reject(error);+ };+
child.stdout?.on("data", (chunk: Buffer) => {
- stdout += chunk.toString();+ const text = chunk.toString();+ stdout += text;+ stdoutBytes += Buffer.byteLength(text);+ if (stdoutBytes > maxBufferBytes) {+ fail(+ new Error(+ `${commandLabel(args)} exceeded stdout buffer limit (${maxBufferBytes} bytes).`,+ ),+ );+ }
});
child.stderr?.on("data", (chunk: Buffer) => {
- stderr += chunk.toString();+ const text = chunk.toString();+ stderr += text;+ stderrBytes += Buffer.byteLength(text);+ if (stderrBytes > maxBufferBytes) {+ fail(+ new Error(+ `${commandLabel(args)} exceeded stderr buffer limit (${maxBufferBytes} bytes).`,+ ),+ );+ }
});
child.on("error", (error) => {
clearTimeout(timeout);
reject(error);
});
child.on("close", (code, signal) => {
+ if (settled) return;+ settled = true;
clearTimeout(timeout);
resolve({ stdout, stderr, code: code ?? null, signal: signal ?? null, timedOut });
});
});
}

As per coding guidelines: Maintain predictable behavior under load and during failures (session restarts, reconnects, partial streams).

Comment threadapps/web/src/components/ChatView.tsx Outdated
Co-authored-by: codex <codex@users.noreply.github.com>
await runGitOrThrow(cwd, args, { allowNonZeroExit });
}

async gitStdout(cwd: string, args: readonly string[], allowNonZeroExit = false): Promise<string> {

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.

🟡 Medium

src/git.ts:359gitStdout discards stderr even on success, so the truncation warning from runGit is lost. Consider checking if output was truncated and either throwing an error or returning a flag so callers know the data is incomplete.

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/git.ts around line 359:
`gitStdout` discards `stderr` even on success, so the truncation warning from `runGit` is lost. Consider checking if output was truncated and either throwing an error or returning a flag so callers know the data is incomplete.

},
);

const rawOutput = (await fs.readFile(outputPath, "utf8")).trim();

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.

🟠 High

src/codexTextGenerator.ts:145fs.readFile at line 145 has no size limit, unlike the maxBufferBytes guard on stdout/stderr in runProcess. Consider adding a file size check (via fs.stat) before reading to prevent OOM if codex produces unexpectedly large output.

- const rawOutput = (await fs.readFile(outputPath, "utf8")).trim();+ const MAX_OUTPUT_BYTES = 8 * 1024 * 1024;+ const stat = await fs.stat(outputPath);+ if (stat.size > MAX_OUTPUT_BYTES) {+ throw new Error(`Codex output exceeded size limit (${MAX_OUTPUT_BYTES} bytes).`);+ }+ const rawOutput = (await fs.readFile(outputPath, "utf8")).trim();

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/codexTextGenerator.ts around line 145:
`fs.readFile` at line 145 has no size limit, unlike the `maxBufferBytes` guard on stdout/stderr in `runProcess`. Consider adding a file size check (via `fs.stat`) before reading to prevent OOM if `codex` produces unexpectedly large output.

- include open PR metadata in git status with graceful `gh` failure handling
- centralize git command execution in `GitCoreService` via `runProcess` with truncation support
- add PTY spawn-helper permission fixes, shell fallback retries, and tests for new behavior
}

async createWorktree(input: GitCreateWorktreeInput): Promise<GitCreateWorktreeResult> {
const sanitizedBranch = input.newBranch.replace(/\//g, "-");

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.

🟡 Medium

src/git.ts:382 Default worktreePath can collide across repos and branches (sanitized names and shared basenames). Suggest deriving a unique, deterministic path (e.g., hash of full repo path + branch) or document if intentional.

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/git.ts around line 382:
Default `worktreePath` can collide across repos and branches (sanitized names and shared basenames). Suggest deriving a unique, deterministic path (e.g., hash of full repo path + branch) or document if intentional.

args.push("-m", trimmedBody);
}
await this.git(cwd, args);
const commitSha = trimStdout(await this.gitStdout(cwd, ["rev-parse", "HEAD"]));

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.

🟢 Low

src/git.ts:256 Race condition: another commit between git commit and git rev-parse HEAD could return the wrong SHA. Consider using git rev-parse HEAD output from the commit command itself, or use git commit --porcelain to get the SHA atomically.

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/git.ts around line 256:
Race condition: another commit between `git commit` and `git rev-parse HEAD` could return the wrong SHA. Consider using `git rev-parse HEAD` output from the commit command itself, or use `git commit --porcelain` to get the SHA atomically.

const worktreeMap = new Map<string, string>();
if (worktreeList.code === 0) {
let currentPath: string | null = null;
for (const line of worktreeList.stdout.split("\n")) {

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.

🟡 Medium

src/git.ts:347 On Windows, split("\n") leaves trailing \r in paths, causing fs.existsSync to fail. Consider using split(/\r?\n/) instead.

Suggested change
for(constlineofworktreeList.stdout.split("\n")){
for(constlineofworktreeList.stdout.split(/\r?\n/)){

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/git.ts around line 347:
On Windows, `split("\n")` leaves trailing `\r` in paths, causing `fs.existsSync` to fail. Consider using `split(/\r?\n/)` instead.

}

async removeWorktree(input: GitRemoveWorktreeInput): Promise<void> {
await executeGit(input.cwd, ["worktree", "remove", input.path], {

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.

🟢 Low

src/git.ts:402 Consider adding -- before input.path to prevent paths starting with - from being interpreted as git options.

Suggested change
awaitexecuteGit(input.cwd,["worktree","remove",input.path],{
awaitexecuteGit(input.cwd,["worktree","remove","--",input.path],{

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/git.ts around line 402:
Consider adding `--` before `input.path` to prevent paths starting with `-` from being interpreted as git options.

- Replace custom inline SVGs with Lucide commit, push, and GitHub icons
- Keep git action behavior unchanged while applying minor style cleanup
Comment threadapps/web/src/components/ChatView.tsx Outdated
juliusmarmingeand others added 3 commits February 12, 2026 12:22
- Add a Git action confirmation modal with live commit/push/PR progress states
- Accept optional `commitMessage` input and skip AI message generation when provided
- Expand server and contracts tests for custom commit message handling
Co-authored-by: codex <codex@users.noreply.github.com>
- Move git menu, modal, and stacked action logic out of `ChatView`
- Render a new `GitActionsControl` wired with `api` and `gitCwd`
- Split Git actions into context-aware Commit, Push, and PR menu items
- Add modal action selection with clearer availability and disabled-state guidance
- Migrate git status and immediate actions to React Query and add a custom GitHub icon
Co-authored-by: codex <codex@users.noreply.github.com>
@coderabbitaicoderabbitaiBot mentioned this pull request Feb 15, 2026
jjalangtry pushed a commit to jjalangtry/t3code that referenced this pull request Mar 16, 2026
dcherrera pushed a commit to dcherrera/t3code that referenced this pull request Jul 7, 2026
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 16, 2026
Makes declarative settings real: a plugin declares a schema and the HOST renders the
form. The plugin ships no form, no RPC, no storage.
The loader now synthesises a settings page from `definition.settings`, so the
declaration is no longer inert (the previous commit shipped the declaration with
nothing consuming it). Generated before register(), so a plugin declaring both gets
its generated page first, then its own; the page id is fixed so a plugin's own
registerSettingsPage cannot shadow it.
PluginSettingsPage wires the concurrency story end to end: the draft carries a
revision, every save sends it back as expectedRevision, and a stale save is rejected
by the server rather than silently clobbering another tab. The server's message is
surfaced verbatim because "does not match the schema" and "changed elsewhere" need
different actions from the user.
The form opens on incompatible stored data and says so, rather than refusing — that
is exactly when the user needs it to repair the values, and the stored data is
preserved until a valid save (Sol spec review MUST pingdotgg#13's recovery half).
This also makes the previously-vacuous test assertable. It now asserts the generated
page appears, and is verified deletion-sensitive: removing the synthesis fails both
new tests. `declared: false` (no schema, or plugin disabled) renders an explanation
rather than an empty form.
Gates: typecheck 0 errors; web 1319 passed; server 1672 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 16, 2026
Sol spec review MUST pingdotgg#13 (detection half). The store recorded a schema fingerprint
from the first commit but nothing ever read it back, so an upgrade that changed a
plugin's schema under already-stored values went undetected — the values were read
as if they still matched, which is exactly the silent misread the fingerprint exists
to prevent.
readDraft now surfaces the fingerprint that produced the stored values, and the
settings RPC compares it against the plugin's CURRENT schema. A mismatch marks the
draft incompatible, which the settings page already renders as "these settings need
attention" with the form open for repair.
The stored data is preserved on mismatch, not discarded: the user may need to read
the old values to reconstruct the new ones, and a plugin that briefly downgrades
should not lose its config. Recovery is a valid save, which rewrites both the values
and the fingerprint.
Tests verified deletion-sensitive: returning a null fingerprint from readDraft fails
both fingerprint tests.
Remaining for MUST pingdotgg#13: hostApi.settings.get should fail InvalidStored on drift
rather than relying on decode alone. The decode already fails for an incompatible
shape in the common case; the fingerprint makes it explicit. Tracked, not claimed.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 16, 2026
Sol spec review MUST pingdotgg#13, plugin-read half. hostApi.settings.get now rejects stored
values whose fingerprint does not match the plugin's current schema, instead of
relying on decode to notice.
Decode is not sufficient and that is the whole point: a widening change (adding an
optional field, relaxing a filter) still decodes cleanly, so the plugin would
silently run on values written for a shape it no longer declares. Comparing the
fingerprint makes the mismatch the fact being checked rather than a side effect of
decoding. The error carries no values — only the plugin id.
KNOWN GAP, stated rather than implied: this path has no test. I wrote one with a
fixture that probes settings.get from a service and records the Exit tag to its data
dir; the probe did not get scheduled within the test's wait, and the "<probe never
ran>" guard I built into it caught that rather than letting it pass vacuously against
whatever I asserted. Rather than ship a test that cannot reliably observe the
behaviour — or one that asserts only "the plugin activated", which would pass with
this check deleted — the tests are removed and the gap recorded.
Store-level fingerprint behaviour IS covered and verified deletion-sensitive
(PluginSettingsStore.test.ts); what is untested is specifically the capability read
path rejecting on drift. Needs a fixture harness that can await a forked service.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 16, 2026
Makes declarative settings real: a plugin declares a schema and the HOST renders the
form. The plugin ships no form, no RPC, no storage.
The loader now synthesises a settings page from `definition.settings`, so the
declaration is no longer inert (the previous commit shipped the declaration with
nothing consuming it). Generated before register(), so a plugin declaring both gets
its generated page first, then its own; the page id is fixed so a plugin's own
registerSettingsPage cannot shadow it.
PluginSettingsPage wires the concurrency story end to end: the draft carries a
revision, every save sends it back as expectedRevision, and a stale save is rejected
by the server rather than silently clobbering another tab. The server's message is
surfaced verbatim because "does not match the schema" and "changed elsewhere" need
different actions from the user.
The form opens on incompatible stored data and says so, rather than refusing — that
is exactly when the user needs it to repair the values, and the stored data is
preserved until a valid save (Sol spec review MUST pingdotgg#13's recovery half).
This also makes the previously-vacuous test assertable. It now asserts the generated
page appears, and is verified deletion-sensitive: removing the synthesis fails both
new tests. `declared: false` (no schema, or plugin disabled) renders an explanation
rather than an empty form.
Gates: typecheck 0 errors; web 1319 passed; server 1672 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 16, 2026
Sol spec review MUST pingdotgg#13 (detection half). The store recorded a schema fingerprint
from the first commit but nothing ever read it back, so an upgrade that changed a
plugin's schema under already-stored values went undetected — the values were read
as if they still matched, which is exactly the silent misread the fingerprint exists
to prevent.
readDraft now surfaces the fingerprint that produced the stored values, and the
settings RPC compares it against the plugin's CURRENT schema. A mismatch marks the
draft incompatible, which the settings page already renders as "these settings need
attention" with the form open for repair.
The stored data is preserved on mismatch, not discarded: the user may need to read
the old values to reconstruct the new ones, and a plugin that briefly downgrades
should not lose its config. Recovery is a valid save, which rewrites both the values
and the fingerprint.
Tests verified deletion-sensitive: returning a null fingerprint from readDraft fails
both fingerprint tests.
Remaining for MUST pingdotgg#13: hostApi.settings.get should fail InvalidStored on drift
rather than relying on decode alone. The decode already fails for an incompatible
shape in the common case; the fingerprint makes it explicit. Tracked, not claimed.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 16, 2026
Sol spec review MUST pingdotgg#13, plugin-read half. hostApi.settings.get now rejects stored
values whose fingerprint does not match the plugin's current schema, instead of
relying on decode to notice.
Decode is not sufficient and that is the whole point: a widening change (adding an
optional field, relaxing a filter) still decodes cleanly, so the plugin would
silently run on values written for a shape it no longer declares. Comparing the
fingerprint makes the mismatch the fact being checked rather than a side effect of
decoding. The error carries no values — only the plugin id.
KNOWN GAP, stated rather than implied: this path has no test. I wrote one with a
fixture that probes settings.get from a service and records the Exit tag to its data
dir; the probe did not get scheduled within the test's wait, and the "<probe never
ran>" guard I built into it caught that rather than letting it pass vacuously against
whatever I asserted. Rather than ship a test that cannot reliably observe the
behaviour — or one that asserts only "the plugin activated", which would pass with
this check deleted — the tests are removed and the gap recorded.
Store-level fingerprint behaviour IS covered and verified deletion-sensitive
(PluginSettingsStore.test.ts); what is untested is specifically the capability read
path rejecting on drift. Needs a fixture harness that can await a forked service.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 17, 2026
Makes declarative settings real: a plugin declares a schema and the HOST renders the
form. The plugin ships no form, no RPC, no storage.
The loader now synthesises a settings page from `definition.settings`, so the
declaration is no longer inert (the previous commit shipped the declaration with
nothing consuming it). Generated before register(), so a plugin declaring both gets
its generated page first, then its own; the page id is fixed so a plugin's own
registerSettingsPage cannot shadow it.
PluginSettingsPage wires the concurrency story end to end: the draft carries a
revision, every save sends it back as expectedRevision, and a stale save is rejected
by the server rather than silently clobbering another tab. The server's message is
surfaced verbatim because "does not match the schema" and "changed elsewhere" need
different actions from the user.
The form opens on incompatible stored data and says so, rather than refusing — that
is exactly when the user needs it to repair the values, and the stored data is
preserved until a valid save (Sol spec review MUST pingdotgg#13's recovery half).
This also makes the previously-vacuous test assertable. It now asserts the generated
page appears, and is verified deletion-sensitive: removing the synthesis fails both
new tests. `declared: false` (no schema, or plugin disabled) renders an explanation
rather than an empty form.
Gates: typecheck 0 errors; web 1319 passed; server 1672 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 17, 2026
Sol spec review MUST pingdotgg#13 (detection half). The store recorded a schema fingerprint
from the first commit but nothing ever read it back, so an upgrade that changed a
plugin's schema under already-stored values went undetected — the values were read
as if they still matched, which is exactly the silent misread the fingerprint exists
to prevent.
readDraft now surfaces the fingerprint that produced the stored values, and the
settings RPC compares it against the plugin's CURRENT schema. A mismatch marks the
draft incompatible, which the settings page already renders as "these settings need
attention" with the form open for repair.
The stored data is preserved on mismatch, not discarded: the user may need to read
the old values to reconstruct the new ones, and a plugin that briefly downgrades
should not lose its config. Recovery is a valid save, which rewrites both the values
and the fingerprint.
Tests verified deletion-sensitive: returning a null fingerprint from readDraft fails
both fingerprint tests.
Remaining for MUST pingdotgg#13: hostApi.settings.get should fail InvalidStored on drift
rather than relying on decode alone. The decode already fails for an incompatible
shape in the common case; the fingerprint makes it explicit. Tracked, not claimed.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 17, 2026
Sol spec review MUST pingdotgg#13, plugin-read half. hostApi.settings.get now rejects stored
values whose fingerprint does not match the plugin's current schema, instead of
relying on decode to notice.
Decode is not sufficient and that is the whole point: a widening change (adding an
optional field, relaxing a filter) still decodes cleanly, so the plugin would
silently run on values written for a shape it no longer declares. Comparing the
fingerprint makes the mismatch the fact being checked rather than a side effect of
decoding. The error carries no values — only the plugin id.
KNOWN GAP, stated rather than implied: this path has no test. I wrote one with a
fixture that probes settings.get from a service and records the Exit tag to its data
dir; the probe did not get scheduled within the test's wait, and the "<probe never
ran>" guard I built into it caught that rather than letting it pass vacuously against
whatever I asserted. Rather than ship a test that cannot reliably observe the
behaviour — or one that asserts only "the plugin activated", which would pass with
this check deleted — the tests are removed and the gap recorded.
Store-level fingerprint behaviour IS covered and verified deletion-sensitive
(PluginSettingsStore.test.ts); what is untested is specifically the capability read
path rejecting on drift. Needs a fixture harness that can await a forked service.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 30, 2026
Makes declarative settings real: a plugin declares a schema and the HOST renders the
form. The plugin ships no form, no RPC, no storage.
The loader now synthesises a settings page from `definition.settings`, so the
declaration is no longer inert (the previous commit shipped the declaration with
nothing consuming it). Generated before register(), so a plugin declaring both gets
its generated page first, then its own; the page id is fixed so a plugin's own
registerSettingsPage cannot shadow it.
PluginSettingsPage wires the concurrency story end to end: the draft carries a
revision, every save sends it back as expectedRevision, and a stale save is rejected
by the server rather than silently clobbering another tab. The server's message is
surfaced verbatim because "does not match the schema" and "changed elsewhere" need
different actions from the user.
The form opens on incompatible stored data and says so, rather than refusing — that
is exactly when the user needs it to repair the values, and the stored data is
preserved until a valid save (Sol spec review MUST pingdotgg#13's recovery half).
This also makes the previously-vacuous test assertable. It now asserts the generated
page appears, and is verified deletion-sensitive: removing the synthesis fails both
new tests. `declared: false` (no schema, or plugin disabled) renders an explanation
rather than an empty form.
Gates: typecheck 0 errors; web 1319 passed; server 1672 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 30, 2026
Sol spec review MUST pingdotgg#13 (detection half). The store recorded a schema fingerprint
from the first commit but nothing ever read it back, so an upgrade that changed a
plugin's schema under already-stored values went undetected — the values were read
as if they still matched, which is exactly the silent misread the fingerprint exists
to prevent.
readDraft now surfaces the fingerprint that produced the stored values, and the
settings RPC compares it against the plugin's CURRENT schema. A mismatch marks the
draft incompatible, which the settings page already renders as "these settings need
attention" with the form open for repair.
The stored data is preserved on mismatch, not discarded: the user may need to read
the old values to reconstruct the new ones, and a plugin that briefly downgrades
should not lose its config. Recovery is a valid save, which rewrites both the values
and the fingerprint.
Tests verified deletion-sensitive: returning a null fingerprint from readDraft fails
both fingerprint tests.
Remaining for MUST pingdotgg#13: hostApi.settings.get should fail InvalidStored on drift
rather than relying on decode alone. The decode already fails for an incompatible
shape in the common case; the fingerprint makes it explicit. Tracked, not claimed.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 30, 2026
Sol spec review MUST pingdotgg#13, plugin-read half. hostApi.settings.get now rejects stored
values whose fingerprint does not match the plugin's current schema, instead of
relying on decode to notice.
Decode is not sufficient and that is the whole point: a widening change (adding an
optional field, relaxing a filter) still decodes cleanly, so the plugin would
silently run on values written for a shape it no longer declares. Comparing the
fingerprint makes the mismatch the fact being checked rather than a side effect of
decoding. The error carries no values — only the plugin id.
KNOWN GAP, stated rather than implied: this path has no test. I wrote one with a
fixture that probes settings.get from a service and records the Exit tag to its data
dir; the probe did not get scheduled within the test's wait, and the "<probe never
ran>" guard I built into it caught that rather than letting it pass vacuously against
whatever I asserted. Rather than ship a test that cannot reliably observe the
behaviour — or one that asserts only "the plugin activated", which would pass with
this check deleted — the tests are removed and the gap recorded.
Store-level fingerprint behaviour IS covered and verified deletion-sensitive
(PluginSettingsStore.test.ts); what is untested is specifically the capability read
path rejecting on drift. Needs a fixture harness that can await a forked service.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 30, 2026
Makes declarative settings real: a plugin declares a schema and the HOST renders the
form. The plugin ships no form, no RPC, no storage.
The loader now synthesises a settings page from `definition.settings`, so the
declaration is no longer inert (the previous commit shipped the declaration with
nothing consuming it). Generated before register(), so a plugin declaring both gets
its generated page first, then its own; the page id is fixed so a plugin's own
registerSettingsPage cannot shadow it.
PluginSettingsPage wires the concurrency story end to end: the draft carries a
revision, every save sends it back as expectedRevision, and a stale save is rejected
by the server rather than silently clobbering another tab. The server's message is
surfaced verbatim because "does not match the schema" and "changed elsewhere" need
different actions from the user.
The form opens on incompatible stored data and says so, rather than refusing — that
is exactly when the user needs it to repair the values, and the stored data is
preserved until a valid save (Sol spec review MUST pingdotgg#13's recovery half).
This also makes the previously-vacuous test assertable. It now asserts the generated
page appears, and is verified deletion-sensitive: removing the synthesis fails both
new tests. `declared: false` (no schema, or plugin disabled) renders an explanation
rather than an empty form.
Gates: typecheck 0 errors; web 1319 passed; server 1672 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 30, 2026
Sol spec review MUST pingdotgg#13 (detection half). The store recorded a schema fingerprint
from the first commit but nothing ever read it back, so an upgrade that changed a
plugin's schema under already-stored values went undetected — the values were read
as if they still matched, which is exactly the silent misread the fingerprint exists
to prevent.
readDraft now surfaces the fingerprint that produced the stored values, and the
settings RPC compares it against the plugin's CURRENT schema. A mismatch marks the
draft incompatible, which the settings page already renders as "these settings need
attention" with the form open for repair.
The stored data is preserved on mismatch, not discarded: the user may need to read
the old values to reconstruct the new ones, and a plugin that briefly downgrades
should not lose its config. Recovery is a valid save, which rewrites both the values
and the fingerprint.
Tests verified deletion-sensitive: returning a null fingerprint from readDraft fails
both fingerprint tests.
Remaining for MUST pingdotgg#13: hostApi.settings.get should fail InvalidStored on drift
rather than relying on decode alone. The decode already fails for an incompatible
shape in the common case; the fingerprint makes it explicit. Tracked, not claimed.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 30, 2026
Sol spec review MUST pingdotgg#13, plugin-read half. hostApi.settings.get now rejects stored
values whose fingerprint does not match the plugin's current schema, instead of
relying on decode to notice.
Decode is not sufficient and that is the whole point: a widening change (adding an
optional field, relaxing a filter) still decodes cleanly, so the plugin would
silently run on values written for a shape it no longer declares. Comparing the
fingerprint makes the mismatch the fact being checked rather than a side effect of
decoding. The error carries no values — only the plugin id.
KNOWN GAP, stated rather than implied: this path has no test. I wrote one with a
fixture that probes settings.get from a service and records the Exit tag to its data
dir; the probe did not get scheduled within the test's wait, and the "<probe never
ran>" guard I built into it caught that rather than letting it pass vacuously against
whatever I asserted. Rather than ship a test that cannot reliably observe the
behaviour — or one that asserts only "the plugin activated", which would pass with
this check deleted — the tests are removed and the gap recorded.
Store-level fingerprint behaviour IS covered and verified deletion-sensitive
(PluginSettingsStore.test.ts); what is untested is specifically the capability read
path rejecting on drift. Needs a fixture harness that can await a forked service.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 31, 2026
Makes declarative settings real: a plugin declares a schema and the HOST renders the
form. The plugin ships no form, no RPC, no storage.
The loader now synthesises a settings page from `definition.settings`, so the
declaration is no longer inert (the previous commit shipped the declaration with
nothing consuming it). Generated before register(), so a plugin declaring both gets
its generated page first, then its own; the page id is fixed so a plugin's own
registerSettingsPage cannot shadow it.
PluginSettingsPage wires the concurrency story end to end: the draft carries a
revision, every save sends it back as expectedRevision, and a stale save is rejected
by the server rather than silently clobbering another tab. The server's message is
surfaced verbatim because "does not match the schema" and "changed elsewhere" need
different actions from the user.
The form opens on incompatible stored data and says so, rather than refusing — that
is exactly when the user needs it to repair the values, and the stored data is
preserved until a valid save (Sol spec review MUST pingdotgg#13's recovery half).
This also makes the previously-vacuous test assertable. It now asserts the generated
page appears, and is verified deletion-sensitive: removing the synthesis fails both
new tests. `declared: false` (no schema, or plugin disabled) renders an explanation
rather than an empty form.
Gates: typecheck 0 errors; web 1319 passed; server 1672 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 31, 2026
Sol spec review MUST pingdotgg#13 (detection half). The store recorded a schema fingerprint
from the first commit but nothing ever read it back, so an upgrade that changed a
plugin's schema under already-stored values went undetected — the values were read
as if they still matched, which is exactly the silent misread the fingerprint exists
to prevent.
readDraft now surfaces the fingerprint that produced the stored values, and the
settings RPC compares it against the plugin's CURRENT schema. A mismatch marks the
draft incompatible, which the settings page already renders as "these settings need
attention" with the form open for repair.
The stored data is preserved on mismatch, not discarded: the user may need to read
the old values to reconstruct the new ones, and a plugin that briefly downgrades
should not lose its config. Recovery is a valid save, which rewrites both the values
and the fingerprint.
Tests verified deletion-sensitive: returning a null fingerprint from readDraft fails
both fingerprint tests.
Remaining for MUST pingdotgg#13: hostApi.settings.get should fail InvalidStored on drift
rather than relying on decode alone. The decode already fails for an incompatible
shape in the common case; the fingerprint makes it explicit. Tracked, not claimed.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 31, 2026
Sol spec review MUST pingdotgg#13, plugin-read half. hostApi.settings.get now rejects stored
values whose fingerprint does not match the plugin's current schema, instead of
relying on decode to notice.
Decode is not sufficient and that is the whole point: a widening change (adding an
optional field, relaxing a filter) still decodes cleanly, so the plugin would
silently run on values written for a shape it no longer declares. Comparing the
fingerprint makes the mismatch the fact being checked rather than a side effect of
decoding. The error carries no values — only the plugin id.
KNOWN GAP, stated rather than implied: this path has no test. I wrote one with a
fixture that probes settings.get from a service and records the Exit tag to its data
dir; the probe did not get scheduled within the test's wait, and the "<probe never
ran>" guard I built into it caught that rather than letting it pass vacuously against
whatever I asserted. Rather than ship a test that cannot reliably observe the
behaviour — or one that asserts only "the plugin activated", which would pass with
this check deleted — the tests are removed and the gap recorded.
Store-level fingerprint behaviour IS covered and verified deletion-sensitive
(PluginSettingsStore.test.ts); what is untested is specifically the capability read
path rejecting on drift. Needs a fixture harness that can await a forked service.
Gates: typecheck 0 errors; server 1675 passed.
aorwall added a commit to aorwall/t3code that referenced this pull request Aug 2, 2026
* docs: spec for preview servers in the T3 Code web app
Approved spec covering the Servers right-panel view, Moatless-owned preview
tabs backed by Redis, and an iframe renderer that lets the existing browser
panel work outside Electron.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat: add the servers.* contract group
Three reads over the servers a thread's environment declares: list them, watch
their status, and follow one's log. Field names are taken verbatim from the
host's own server record so the server side is a rename-free serialization.
Nothing here starts, stops or reconfigures a server; those methods are
specified elsewhere and deliberately out of scope.
The preview module docstring said the preview is desktop-only and that the
desktop renderer mediates. Both stop being true in the same change, so the
docstring now describes the surface by capability rather than by client.
* feat: carry the servers.* group through the client runtime and reference server
Client-side atoms for the three reads, with the log subscription folding
lines into a bounded buffer so a remounted panel keeps what it received
rather than starting over.
The reference server declares no thread servers - it runs threads on the
machine it is on - so it answers the list empty and both subscriptions
silent. A hosted environment answers the same three methods with real data.
Also splits the preview runtime capability in two. One boolean was answering
both "can this runtime show a page" and "can this runtime read the page it
shows", which are the same question only on the desktop app.
* feat: host preview pages in a sandboxed frame on the web
The web app could not show a task's page at all. It can now: one frame per
open tab, mounted at the app root so it survives the panel collapsing, and
positioned by the same surface rect the desktop webview uses.
The frame is driven and never read. Navigation writes to the server and the
frame follows; refresh replaces the element because a cross-origin frame has
no reload and reassigning src would grow the parent's history; back and
forward are absent rather than dead.
Two things a frame cannot report, the panel says from elsewhere. A page that
is not there is explained by what its server says about itself, which is
better than a net error - "installing" is an answer no net error carries. A
page that renders nothing while its server says it is running gets a hint
offered as a hint, because a frame-ancestors refusal and the preview host's
own 401 look identical from outside and neither fires an event.
* feat: add a Servers view to the right panel
One row per server the thread's environment declares, with its status kept
current by the subscription, its log on demand, and an Open that hands the
URL to a browser tab.
Everything in it is a read - a row that says failed offers its log and no
button. Restarting a server is a write and lands elsewhere.
The right panel's persisted state moves to version 8 for the new surface
kind, and its migration now drops surfaces whose kind this build does not
know. That is what makes the version bump safe to downgrade away from: the
rest of the workspace survives and only the unknown tab is lost.
* test: cover the browser preview surface and the servers view
Two products share these schemas and no test process, so the seam is a set
of real Moatless responses checked in here and decoded by the schemas
themselves. When its projection changes the fixture changes with it in one
commit, and the decode test is what fails if the two drift.
The rest covers what the change actually promises: the capability answers
three runtimes, the chrome row omits controls rather than disabling them,
the frame re-keys instead of reassigning src, exactly one browser host
renders, and the panel state survives the version bump while dropping a kind
this build does not know.
* fix: state the environment's absence rather than implying it from a row
The never-provisioned fixture claimed a server is listed as `stopped` with
no URL. It is not. Moatless resolves status config-first from a NotFound
pod, which falls back to `starting` with the ingress URL the port will have
— so the panel showed `starting` forever for an environment that does not
exist, and offered an Open button pointing at a 502.
The fixture now carries what the backend produces, and the panel states the
environment's own status above the list instead of leaving it to be inferred
from rows that cannot say it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: track the fork's own delta, not just what upstream does to it
The policy could answer "who wins this conflict" but not "what did we change",
and the second is the question you are actually asking when git stops on a line
you do not recognise. The path table only ever listed files we expected to fight
over — a much smaller set than the files we changed. The whole thread-servers
group, around forty files, appeared nowhere.
§3 is that missing list, and a fifth hazard in the preamble names the failure it
prevents: a conflict resolved toward upstream because nobody could tell our line
was deliberate. Two consequences: the checklist asks for a row in the same commit
as the change rather than at the next merge, and reading the inventory is now a
step in resolving a conflict rather than something to remember.
Also adds path-policy rows for the highest-risk of those files —
`apps/server/src/ws.ts` and `RpcAuthorization.ts`, the only upstream server files
the fork touches, where the resolution is to take theirs and re-add three
`servers.*` entries.
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
aorwall pushed a commit to aorwall/t3code that referenced this pull request Aug 12, 2026
The 2026-08-08 upstream merge took 89 minutes. Most of that was not conflict
resolution; it was performing checks that existed only as prose in
docs/fork/upstream-merge-inventory.md, one at a time, and rediscovering two
things the doc could have told me before I started.
Five changes, in the order they pay off.
**The inventory is data now.** docs/fork/inventory.json holds the fork-owned
concerns, path policy, fork inventory, tripwires, deliberately deleted upstream
paths, off-repository state and convergence entries. The markdown keeps only
what a check cannot hold — the four re-application deltas, where the
unsupported-method set comes from, and the reasoning behind the path policy
rule — and drops from 784 lines to 243.
**The checks run.** Five dependency-free scripts under the skill:
- `preflight.mjs` — the range, stale entries, the owned-concern sweep, and the
conflict forecast: every file both sides touched, grouped by the verdict that
resolves it, before the merge starts.
- `inventory-check.mjs` — every inventory path read back out of upstream/main.
This is the one that matters: last merge, upstream moved SidebarV2's content
into Sidebar.tsx and Sidebar.tsx's into LegacySidebar.tsx. Git cannot see a
content swap as a rename — it is a delete paired with a modify, which no -M
threshold detects — so it surfaced as a modify/delete conflict mid-merge.
Run against the pre-merge tree, this check names it in about a second.
- `tripwires.mjs`, `unsupported-methods.mjs`, `verify.mjs`.
Running inventory-check against the current tree immediately found three dead
path-policy entries that had been faithfully transcribed forward through
several merges, for directories deleted in pingdotgg#13 and pingdotgg#26.
**Verification is one command, earlier.** `verify.mjs` runs tripwires, the
unsupported-method derivation, format, lint, types and tests — and does not
stop at the first failure, so a formatting nit no longer hides the type errors
behind it. It raises the heap the web suite needs, whose failure mode is
otherwise an exit 137 that reads like a real test failure. The skill now runs
it *before* the documentation steps, because its output is their input.
**Counts come out of prose.** Both snapshots the last merge relied on were
stale. "38 of 87 methods" and "Clerk 4 / pairing 73 / session bootstrap 9" are
replaced by the command that derives them.
**The fork test no longer hard-codes upstream paths.** features.test.ts read
`../components/Sidebar.tsx?raw`; when upstream renamed that file the whole
suite failed to build, at a module path, saying nothing about the gate. It now
reads the guarded files out of inventory.json and reaches them through
import.meta.glob, so a rename fails as a named assertion that says which entry
to re-point — and the test and the merge scripts read the same guards, so they
cannot drift apart.
Verified: fmt, lint, typecheck and 1908 tests pass. `tripwires.mjs` reports one
finding, which is genuine and already tracked — thread-transfer-report.yml went
active on GitHub when the merge branch was pushed and still needs disabling.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
NeilTheFisher pushed a commit to NeilTheFisher/t3code that referenced this pull request Aug 18, 2026
AFLabAI added a commit to AFLabAI/t3code that referenced this pull request Sep 2, 2026
Increase diagnostic visibility by appending complete vp check stdout/stderr to job summary instead of truncating. This exposes all 11 lint errors detected in RUN pingdotgg#13.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Sign up for freeto 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

@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: Commit, Push and Create PR actions - #13

Merged
juliusmarminge merged 18 commits into
mainfrom
codex/add-github-commit-push-ui
Feb 12, 2026
Merged

feat: Commit, Push and Create PR actions#13
juliusmarminge merged 18 commits into
mainfrom
codex/add-github-commit-push-ui

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Feb 12, 2026

Copy link
Copy Markdown
Member

Open with Devin

Summary by CodeRabbit

  • New Features

    • AI-powered generation for commit messages and PR title/body.
    • New Git core & manager with stacked workflows (commit, commit & push, commit & push & create/open PR), richer status details, and WebSocket/native endpoints to run Git status and actions.
    • UI: Git actions menu with real-time status, action execution, notices and error handling.
  • Chores

    • Robust process runner with buffer and timeout safeguards.
  • Tests

    • Extensive end-to-end and unit tests covering Git flows, PR lifecycle, manager behavior, and contract schemas.

@coderabbitai

coderabbitaiBot commented Feb 12, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds a Git subsystem: process runner with buffer/timeouts, Git core service, GitManager orchestration, Codex-based commit/PR generation, WS API + server wiring, client UI integration, contracts/schemas for stacked git actions, and extensive tests.

Changes

Cohort / File(s)Summary
Text Generation Service
apps/server/src/coreServices.ts, apps/server/src/codexTextGenerator.ts
Defines TextGenerationService types and implements CodexTextGenerator with JSON schemas, validation/parsers, temp-file helpers, runCodexJson orchestration, and methods to generate commit messages and PR content.
Process Execution
apps/server/src/processRunner.ts
Adds runProcess with configurable maxBufferBytes, stdout/stderr accumulation and byte limits, timeout handling (SIGTERM→SIGKILL), spawn/exit normalization, and detailed error types.
Git Core Service
apps/server/src/git.ts
Introduces GitCoreService and helpers: enriched status/statusDetails, prepareCommitContext, commit, pushCurrentBranch, readRangeContext, readConfigValue, runGit helpers, and upstream/default-branch logic.
GitManager Orchestration
apps/server/src/gitManager.ts
Adds GitManager coordinating gitCore, processRunner, Codex text generation, and gh interactions for stacked actions (commit, push, PR), PR discovery/creation, temp-file PR bodies, and error normalization.
Server WS Integration & Tests
apps/server/src/wsServer.ts, apps/server/src/wsServer.test.ts
Wires GitManager into server options, exposes WS methods git.status and git.runStackedAction, and updates tests to inject/validate gitManager delegation and error propagation.
Tests: GitManager & GitCore
apps/server/src/gitManager.test.ts, apps/server/src/git.test.ts
Adds end-to-end tests covering stacked actions, GH CLI scenarios, upstream behavior, detached HEAD, auth errors, and GitCoreService behaviors.
Client Integration
apps/web/src/wsNativeApi.ts, apps/web/src/components/ChatView.tsx
Extends native API with git.status and git.runStackedAction; integrates Git actions menu and UI state into ChatView with status polling, action execution, and notices.
Contracts / IPC / WS Methods & Tests
packages/contracts/src/git.ts, packages/contracts/src/git.test.ts, packages/contracts/src/ipc.ts, packages/contracts/src/ws.ts
Adds Zod schemas/types for git status and stacked actions, updates NativeApi.git signatures, expands WS_METHODS with git.status and git.runStackedAction, and adds schema tests.

Sequence Diagram(s)

sequenceDiagram
participant Client as Client (ChatView)
participant WS as WebSocket Server
participant GM as GitManager
participant GC as GitCoreService
participant Proc as ProcessRunner
participant Codex as Codex Service
participant GH as GitHub CLI
Client->>WS: git.runStackedAction(action, cwd)
WS->>GM: runStackedAction()
rect rgba(100,150,200,0.5)
Note over GM: Commit Step
GM->>GC: prepareCommitContext(cwd)
GC-->>GM: stagedSummary, stagedPatch
GM->>Codex: generateCommitMessage(diff)
Codex-->>GM: subject, body
GM->>Proc: git commit -m "..."
Proc-->>GM: commit result
end
rect rgba(100,200,150,0.5)
Note over GM: Push Step (if requested)
GM->>GC: pushCurrentBranch(cwd, upstream?)
GC-->>GM: push result
end
rect rgba(200,150,100,0.5)
Note over GM: PR Step (if requested)
GM->>GC: readRangeContext(base, head)
GC-->>GM: commitSummary, diffSummary, diffPatch
GM->>Codex: generatePrContent(rangeContext)
Codex-->>GM: title, body
GM->>GH: gh pr list --head branch
GH-->>GM: existing PRs
alt PR exists
GM->>GH: gh pr view PR_NUMBER
GH-->>GM: PR details
else
GM->>GH: gh pr create --title "..." --body file://tmp
GH-->>GM: new PR info
end
end
GM-->>WS: GitRunStackedActionResult
WS-->>Client: result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 2.56% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title 'feat: Commit, Push and Create PR actions' accurately captures the main feature added: three new Git workflow actions integrated into the UI and backend services.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch codex/add-github-commit-push-ui

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps

greptile-appsBot commented Feb 12, 2026

Copy link
Copy Markdown

Greptile Overview

Greptile Summary

This PR implements comprehensive Git stacked actions (commit, push, create PR) with AI-powered text generation for commit messages and PR content. The implementation spans server-side orchestration, robust process execution, WebSocket/IPC integration, and a polished React UI with real-time progress tracking.

Key changes:

  • New GitManager orchestrates stacked workflows (commit → push → PR) with proper error handling and GitHub CLI integration
  • GitCoreService provides low-level Git operations using direct spawn (no shell), with buffer management and timeout safeguards
  • CodexTextGenerator generates commit messages and PR content via Codex CLI with structured JSON output schemas
  • ProcessRunner implements robust subprocess execution with truncation support, graceful termination, and buffer limit enforcement
  • GitActionsControl UI component provides modal-based workflow with step-by-step progress, custom commit messages, and error states
  • Comprehensive test coverage across unit tests (git.test.ts, processRunner.test.ts) and integration tests (gitManager.test.ts)
  • WebSocket and IPC layers extended to expose git.status and git.runStackedAction methods to both web and desktop clients

Architecture aligns with project priorities:

  • Performance: Direct spawn without shell overhead, buffer limits prevent memory issues
  • Reliability: Proper timeout handling, graceful degradation (PR lookup is best-effort), temp file cleanup
  • Predictable behavior: Structured status tracking, deterministic base branch resolution, comprehensive error normalization

Confidence Score: 5/5

  • This PR is safe to merge with high confidence
  • The implementation demonstrates strong engineering discipline: comprehensive test coverage (490 lines in gitManager.test.ts alone), proper error handling with normalized error messages, robust subprocess management with buffer limits and timeouts, dependency injection for testability, and alignment with project priorities (performance, reliability, predictable behavior). The only noted issue is duplicate commit message sanitization which is cosmetic and doesn't affect functionality.
  • No files require special attention

Important Files Changed

FilenameOverview
apps/server/src/git.tsAdds comprehensive Git operations with robust error handling, proper spawn arguments, and buffer management for status, commit, push, and range context operations
apps/server/src/gitManager.tsImplements high-level orchestration for stacked Git workflows (commit, push, PR) with AI text generation, proper dependency injection, and GitHub CLI integration
apps/server/src/codexTextGenerator.tsImplements AI-powered text generation for commit messages and PR content using Codex CLI with structured JSON output, proper sanitization, and temp file cleanup
apps/server/src/processRunner.tsAdds robust process execution with buffer limit enforcement, timeout handling, graceful termination (SIGTERM then SIGKILL), and truncation support
apps/server/src/wsServer.tsIntegrates GitManager into WebSocket server to expose git.status and git.runStackedAction methods via WS protocol
packages/contracts/src/git.tsExtends Git contracts with Zod schemas for stacked action API (status, commit, push, PR) with comprehensive validation rules
apps/web/src/components/GitActionsControl.tsxImplements comprehensive Git actions UI with modal workflow, real-time progress tracking, step-by-step execution, error handling, and PR link opening

Sequence Diagram

sequenceDiagram
participant User
participant GitActionsControl
participant NativeApi
participant GitManager
participant GitCore
participant CodexTextGenerator
participant GitCLI
participant GitHubCLI
User->>GitActionsControl: Click "Commit and create PR"
GitActionsControl->>GitActionsControl: Open modal, set action
User->>GitActionsControl: Confirm action
GitActionsControl->>NativeApi: git.runStackedAction(commit)
NativeApi->>GitManager: runStackedAction(commit)
GitManager->>GitCore: statusDetails(cwd)
GitCore->>GitCLI: git status --porcelain=2
GitCLI-->>GitCore: status output
GitCore-->>GitManager: branch, upstream info
GitManager->>GitCore: prepareCommitContext(cwd)
GitCore->>GitCLI: git add -A
GitCore->>GitCLI: git diff --cached
GitCLI-->>GitCore: staged changes
GitCore-->>GitManager: stagedSummary, stagedPatch
GitManager->>CodexTextGenerator: generateCommitMessage()
CodexTextGenerator->>CodexTextGenerator: Write temp schema file
CodexTextGenerator->>GitCLI: codex exec --output-schema
GitCLI-->>CodexTextGenerator: JSON response
CodexTextGenerator->>CodexTextGenerator: Parse, sanitize, cleanup
CodexTextGenerator-->>GitManager: {subject, body}
GitManager->>GitCore: commit(cwd, subject, body)
GitCore->>GitCLI: git commit -m subject -m body
GitCLI-->>GitCore: success
GitCore-->>GitManager: {commitSha}
GitManager-->>NativeApi: commit result
NativeApi-->>GitActionsControl: Update progress: commit completed
GitActionsControl->>NativeApi: git.runStackedAction(commit_push)
NativeApi->>GitManager: runStackedAction(commit_push)
GitManager->>GitCore: pushCurrentBranch(cwd)
GitCore->>GitCLI: git push -u origin branch
GitCLI-->>GitCore: success
GitCore-->>GitManager: {status: pushed, branch}
GitManager-->>NativeApi: push result
NativeApi-->>GitActionsControl: Update progress: push completed
GitActionsControl->>NativeApi: git.runStackedAction(commit_push_pr)
NativeApi->>GitManager: runStackedAction(commit_push_pr)
GitManager->>GitHubCLI: gh pr list --head branch
GitHubCLI-->>GitManager: [] (no existing PR)
GitManager->>GitCore: readRangeContext(cwd, baseBranch)
GitCore->>GitCLI: git log, git diff
GitCLI-->>GitCore: commit history, diff
GitCore-->>GitManager: rangeContext
GitManager->>CodexTextGenerator: generatePrContent()
CodexTextGenerator->>GitCLI: codex exec --output-schema
GitCLI-->>CodexTextGenerator: JSON response
CodexTextGenerator-->>GitManager: {title, body}
GitManager->>GitHubCLI: gh pr create --title --body-file
GitHubCLI-->>GitManager: PR URL
GitManager->>GitHubCLI: gh pr view --web
GitHubCLI-->>GitManager: success
GitManager-->>NativeApi: pr result
NativeApi-->>GitActionsControl: Update progress: PR created
GitActionsControl->>User: Show completion with PR link
Loading

Last reviewed commit: dd92084

@greptile-appsgreptile-appsBot 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.

14 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment threadapps/server/src/gitManager.ts

@coderabbitaicoderabbitaiBot 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.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@apps/server/src/processRunner.ts`:
- Around line 128-131: Attach an 'error' listener to the child's stdin and use
the write callback to handle possible write errors before calling end: in
processRunner.ts, before calling child.stdin.write(...) register
child.stdin.once("error", err => {/* reject/forward error or cleanup and ensure
promise rejects */}), call child.stdin.write(options.stdin, (err) => { if (err)
{ /* handle/reject/cleanup */ } child.stdin.end(); }); and remove the
unconditional child.stdin.end() so we only end after the write completes; ensure
any error handling forwards the error to the same rejection/cleanup path used by
child.once("error") for the spawned process.
🧹 Nitpick comments (2)
packages/contracts/src/git.test.ts (1)

11-71: LGTM!

The tests provide good coverage for schema validation, including whitespace trimming and nested field parsing.

Consider adding negative test cases to verify that invalid inputs are rejected (e.g., invalid action strings, missing required fields). This would strengthen the contract validation.

,

apps/server/src/codexTextGenerator.ts (1)

84-103: Consider consolidating duplicate sanitization logic.

sanitizeCommitSubject here (lines 84-95) and sanitizeCommitMessage in gitManager.ts (lines 99-110) perform nearly identical operations: extracting the first line, removing trailing periods, and truncating to 72 characters. This duplication could lead to divergent behavior over time.

Consider extracting a shared utility or having gitManager.ts rely on the already-sanitized output from CodexTextGenerator without additional sanitization.

Comment threadapps/server/src/processRunner.ts
Comment threadapps/server/src/gitManager.ts Outdated
Comment threadapps/server/src/processRunner.ts
Comment threadapps/server/src/processRunner.ts
Comment threadapps/server/src/wsServer.ts
Comment threadapps/server/src/gitManager.ts Outdated
Comment threadapps/web/src/components/ChatView.tsx Outdated
Comment threadapps/server/src/codexTextGenerator.ts Outdated
@macroscopeapp

macroscopeappBot commented Feb 12, 2026

Copy link
Copy Markdown
Contributor

Add Commit, Push, and Create PR actions by introducing GitCoreService, GitManager.runStackedAction, and a web GitActionsControl wired through WS and IPC APIs

Implements stacked git workflows across server and web: adds GitCoreService for git operations, CodexTextGenerator for commit/PR text, and GitManager orchestration; exposes git.status and git.runStackedAction over WebSocket; adds desktop bridge shell.openExternal; and introduces a GitActionsControl UI to run commit/push/PR. Terminal spawning gains multi-shell fallback and runProcess provides standardized subprocess handling. See apps/server/src/gitManager.ts, apps/server/src/git.ts, apps/server/src/codexTextGenerator.ts, and apps/web/src/components/GitActionsControl.tsx.

📍Where to Start

Start with the orchestration entrypoint GitManager.runStackedAction in apps/server/src/gitManager.ts, then review GitCoreService in apps/server/src/git.ts and the web client GitActionsControl in apps/web/src/components/GitActionsControl.tsx.


Macroscope summarized dd92084.

Co-authored-by: codex <codex@users.noreply.github.com>
Comment threadapps/server/src/gitManager.ts

@coderabbitaicoderabbitaiBot 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.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@apps/web/src/components/ChatView.tsx`:
- Around line 395-424: When a git status fetch succeeds we need to clear any
previous error so the error banner doesn't persist; inside the load async
function (the one that calls api.git.status with gitCwd) after successfully
calling setGitStatus(nextStatus) also call setGitActionError(null) (guarded by
the same !cancelled check) so successful refreshes remove stale errors; update
the useEffect's load success branch in ChatView.tsx (the load function /
useEffect that references api, gitCwd, setGitStatus, setGitActionError)
accordingly.

Comment threadapps/web/src/components/ChatView.tsx Outdated
Comment threadapps/server/src/git.ts
Comment threadapps/server/src/git.ts Outdated

@coderabbitaicoderabbitaiBot 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.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@apps/server/src/git.test.ts`:
- Around line 460-466: The test invokes git with a single-quoted remote path
which fails on Windows; update the call that runs git(tmp.path, `remote add
origin '${remote.path}'`) to wrap the path using JSON.stringify(remote.path)
(i.e., produce a double-quoted, escaped string) so Windows cmd handles spaces
correctly; modify the test where makeTmpDir()/remote and the git(...) invocation
are used (see git(tmp.path, `remote add origin ...`), initRepoWithCommit,
createGitBranch) to pass the JSON.stringify-wrapped path instead of single
quotes.
In `@apps/server/src/git.ts`:
- Around line 245-251: The current early-return treats a branch as
"skipped_up_to_date" when details.hasUpstream && details.aheadCount === 0 even
if it is behind; update the condition to also require details.behindCount === 0
so we only mark truly up-to-date branches. Change the if that checks
details.hasUpstream and details.aheadCount to: details.hasUpstream &&
details.aheadCount === 0 && details.behindCount === 0 (leaving the returned
object with branch and optional upstreamBranch unchanged).
- Around line 129-143: The timeout check must be unconditional: in
runGitOrThrow, always throw when result.timedOut by calling
normalizeGitExecutionError(args, result) (or similar) before considering
options.allowNonZeroExit; then keep the existing non-zero exit handling for
result.code when options.allowNonZeroExit is false. Update the logic in
runGitOrThrow (referencing runGitOrThrow, RunGitOptions,
options.allowNonZeroExit, result.timedOut, result.code, and
normalizeGitExecutionError) so timeouts are detected and thrown unconditionally
while allowing suppressed non-zero exit codes only when appropriate.
In `@apps/server/src/gitManager.ts`:
- Around line 402-423: Both runGh and runGhStdout currently call this.run("gh",
args, { cwd }) and can hang; add an explicit timeoutMs option to those calls.
Define a clear constant (e.g. GH_CLI_TIMEOUT_MS = 30_000) near the top of the
module and pass it into this.run as { cwd, timeoutMs: GH_CLI_TIMEOUT_MS } in
both runGh and runGhStdout so gh CLI invocations time out predictably. Ensure
the constant is used in both functions and adjust any types if needed to match
ProcessRunOptions.
🧹 Nitpick comments (1)
apps/server/src/gitManager.ts (1)

175-183: If no commit is created, skip push/PR to avoid empty actions.

When runCommitStep returns skipped_no_changes, the current flow still pushes and can attempt PR creation. That can create confusing “no‑op” PRs or unnecessary network calls.

Comment threadapps/server/src/git.test.ts
Comment threadapps/server/src/git.ts
Comment threadapps/server/src/git.ts Outdated
Comment threadapps/server/src/gitManager.ts
@juliusmarmingejuliusmarminge changed the title Add stacked GitHub action workflowGithubFeb 12, 2026
@juliusmarmingejuliusmarminge changed the title Githubfeat: Commit, Push and Create PR actionsFeb 12, 2026
Co-authored-by: codex <codex@users.noreply.github.com>
Comment on lines +123 to +128
const fail = (error: Error): void => {
child.kill("SIGTERM");
finalize(() => {
reject(error);
});
};

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.

🟡 Medium

src/processRunner.ts:123 The fail function clears the timeout that would escalate to SIGKILL, so if the child ignores SIGTERM, the parent hangs forever. Consider adding a force-kill timer in fail similar to the timeout logic (lines 108-110).

- const fail = (error: Error): void => {- child.kill("SIGTERM");- finalize(() => {- reject(error);- });+ const fail = (error: Error): void => {+ child.kill("SIGTERM");+ const killTimer = setTimeout(() => {+ child.kill("SIGKILL");+ }, 1_000);+ finalize(() => {+ clearTimeout(killTimer);+ reject(error);+ });

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/processRunner.ts around lines 123-128:
The `fail` function clears the timeout that would escalate to `SIGKILL`, so if the child ignores `SIGTERM`, the parent hangs forever. Consider adding a force-kill timer in `fail` similar to the timeout logic (lines 108-110).

Comment threadapps/server/src/processRunner.ts Outdated
stream: "stdout" | "stderr",
chunk: Buffer | string,
): Error | null => {
const text = chunk.toString();

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.

🟢 Low

src/processRunner.ts:134 Using chunk.toString() per Buffer can split multi‑byte UTF‑8 and corrupt text; consider StringDecoder to assemble stdout/stderr safely.

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/processRunner.ts around line 134:
Using `chunk.toString()` per `Buffer` can split multi‑byte UTF‑8 and corrupt text; consider `StringDecoder` to assemble `stdout`/`stderr` safely.

@coderabbitaicoderabbitaiBot 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/server/src/git.ts (1)

50-86: ⚠️ Potential issue | 🟠 Major

Add output caps to avoid unbounded git command buffering.
runGit accumulates stdout/stderr without limits; large diffs can spike memory and make behavior unpredictable under load. Consider enforcing a max buffer (similar to processRunner) or streaming to disk with a safe cap.

🔧 Suggested fix (bounded output)
+const DEFAULT_GIT_MAX_BUFFER_BYTES = 8 * 1024 * 1024;+-function runGit(args: readonly string[], cwd: string, timeoutMs = 30_000): Promise<TerminalCommandResult> {+function runGit(+ args: readonly string[],+ cwd: string,+ timeoutMs = 30_000,+ maxBufferBytes = DEFAULT_GIT_MAX_BUFFER_BYTES,+): Promise<TerminalCommandResult> {
return new Promise((resolve, reject) => {
const child = spawn("git", args, {
cwd,
env: process.env,
stdio: ["ignore", "pipe", "pipe"],
});
let stdout = "";
let stderr = "";
let timedOut = false;
+ let stdoutBytes = 0;+ let stderrBytes = 0;+ let settled = false;
const timeout = setTimeout(() => {
timedOut = true;
child.kill("SIGTERM");
setTimeout(() => {
if (!child.killed) child.kill("SIGKILL");
}, 1_000).unref();
}, timeoutMs);
+ const fail = (error: Error) => {+ if (settled) return;+ settled = true;+ clearTimeout(timeout);+ child.kill("SIGTERM");+ reject(error);+ };+
child.stdout?.on("data", (chunk: Buffer) => {
- stdout += chunk.toString();+ const text = chunk.toString();+ stdout += text;+ stdoutBytes += Buffer.byteLength(text);+ if (stdoutBytes > maxBufferBytes) {+ fail(+ new Error(+ `${commandLabel(args)} exceeded stdout buffer limit (${maxBufferBytes} bytes).`,+ ),+ );+ }
});
child.stderr?.on("data", (chunk: Buffer) => {
- stderr += chunk.toString();+ const text = chunk.toString();+ stderr += text;+ stderrBytes += Buffer.byteLength(text);+ if (stderrBytes > maxBufferBytes) {+ fail(+ new Error(+ `${commandLabel(args)} exceeded stderr buffer limit (${maxBufferBytes} bytes).`,+ ),+ );+ }
});
child.on("error", (error) => {
clearTimeout(timeout);
reject(error);
});
child.on("close", (code, signal) => {
+ if (settled) return;+ settled = true;
clearTimeout(timeout);
resolve({ stdout, stderr, code: code ?? null, signal: signal ?? null, timedOut });
});
});
}

As per coding guidelines: Maintain predictable behavior under load and during failures (session restarts, reconnects, partial streams).

Comment threadapps/web/src/components/ChatView.tsx Outdated
Co-authored-by: codex <codex@users.noreply.github.com>
await runGitOrThrow(cwd, args, { allowNonZeroExit });
}

async gitStdout(cwd: string, args: readonly string[], allowNonZeroExit = false): Promise<string> {

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.

🟡 Medium

src/git.ts:359gitStdout discards stderr even on success, so the truncation warning from runGit is lost. Consider checking if output was truncated and either throwing an error or returning a flag so callers know the data is incomplete.

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/git.ts around line 359:
`gitStdout` discards `stderr` even on success, so the truncation warning from `runGit` is lost. Consider checking if output was truncated and either throwing an error or returning a flag so callers know the data is incomplete.

},
);

const rawOutput = (await fs.readFile(outputPath, "utf8")).trim();

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.

🟠 High

src/codexTextGenerator.ts:145fs.readFile at line 145 has no size limit, unlike the maxBufferBytes guard on stdout/stderr in runProcess. Consider adding a file size check (via fs.stat) before reading to prevent OOM if codex produces unexpectedly large output.

- const rawOutput = (await fs.readFile(outputPath, "utf8")).trim();+ const MAX_OUTPUT_BYTES = 8 * 1024 * 1024;+ const stat = await fs.stat(outputPath);+ if (stat.size > MAX_OUTPUT_BYTES) {+ throw new Error(`Codex output exceeded size limit (${MAX_OUTPUT_BYTES} bytes).`);+ }+ const rawOutput = (await fs.readFile(outputPath, "utf8")).trim();

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/codexTextGenerator.ts around line 145:
`fs.readFile` at line 145 has no size limit, unlike the `maxBufferBytes` guard on stdout/stderr in `runProcess`. Consider adding a file size check (via `fs.stat`) before reading to prevent OOM if `codex` produces unexpectedly large output.

- include open PR metadata in git status with graceful `gh` failure handling
- centralize git command execution in `GitCoreService` via `runProcess` with truncation support
- add PTY spawn-helper permission fixes, shell fallback retries, and tests for new behavior
}

async createWorktree(input: GitCreateWorktreeInput): Promise<GitCreateWorktreeResult> {
const sanitizedBranch = input.newBranch.replace(/\//g, "-");

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.

🟡 Medium

src/git.ts:382 Default worktreePath can collide across repos and branches (sanitized names and shared basenames). Suggest deriving a unique, deterministic path (e.g., hash of full repo path + branch) or document if intentional.

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/git.ts around line 382:
Default `worktreePath` can collide across repos and branches (sanitized names and shared basenames). Suggest deriving a unique, deterministic path (e.g., hash of full repo path + branch) or document if intentional.

args.push("-m", trimmedBody);
}
await this.git(cwd, args);
const commitSha = trimStdout(await this.gitStdout(cwd, ["rev-parse", "HEAD"]));

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.

🟢 Low

src/git.ts:256 Race condition: another commit between git commit and git rev-parse HEAD could return the wrong SHA. Consider using git rev-parse HEAD output from the commit command itself, or use git commit --porcelain to get the SHA atomically.

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/git.ts around line 256:
Race condition: another commit between `git commit` and `git rev-parse HEAD` could return the wrong SHA. Consider using `git rev-parse HEAD` output from the commit command itself, or use `git commit --porcelain` to get the SHA atomically.

const worktreeMap = new Map<string, string>();
if (worktreeList.code === 0) {
let currentPath: string | null = null;
for (const line of worktreeList.stdout.split("\n")) {

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.

🟡 Medium

src/git.ts:347 On Windows, split("\n") leaves trailing \r in paths, causing fs.existsSync to fail. Consider using split(/\r?\n/) instead.

Suggested change
for(constlineofworktreeList.stdout.split("\n")){
for(constlineofworktreeList.stdout.split(/\r?\n/)){

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/git.ts around line 347:
On Windows, `split("\n")` leaves trailing `\r` in paths, causing `fs.existsSync` to fail. Consider using `split(/\r?\n/)` instead.

}

async removeWorktree(input: GitRemoveWorktreeInput): Promise<void> {
await executeGit(input.cwd, ["worktree", "remove", input.path], {

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.

🟢 Low

src/git.ts:402 Consider adding -- before input.path to prevent paths starting with - from being interpreted as git options.

Suggested change
awaitexecuteGit(input.cwd,["worktree","remove",input.path],{
awaitexecuteGit(input.cwd,["worktree","remove","--",input.path],{

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/git.ts around line 402:
Consider adding `--` before `input.path` to prevent paths starting with `-` from being interpreted as git options.

- Replace custom inline SVGs with Lucide commit, push, and GitHub icons
- Keep git action behavior unchanged while applying minor style cleanup
Comment threadapps/web/src/components/ChatView.tsx Outdated
juliusmarmingeand others added 3 commits February 12, 2026 12:22
- Add a Git action confirmation modal with live commit/push/PR progress states
- Accept optional `commitMessage` input and skip AI message generation when provided
- Expand server and contracts tests for custom commit message handling
Co-authored-by: codex <codex@users.noreply.github.com>
- Move git menu, modal, and stacked action logic out of `ChatView`
- Render a new `GitActionsControl` wired with `api` and `gitCwd`
- Split Git actions into context-aware Commit, Push, and PR menu items
- Add modal action selection with clearer availability and disabled-state guidance
- Migrate git status and immediate actions to React Query and add a custom GitHub icon
Co-authored-by: codex <codex@users.noreply.github.com>
@coderabbitaicoderabbitaiBot mentioned this pull request Feb 15, 2026
jjalangtry pushed a commit to jjalangtry/t3code that referenced this pull request Mar 16, 2026
dcherrera pushed a commit to dcherrera/t3code that referenced this pull request Jul 7, 2026
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 16, 2026
Makes declarative settings real: a plugin declares a schema and the HOST renders the
form. The plugin ships no form, no RPC, no storage.
The loader now synthesises a settings page from `definition.settings`, so the
declaration is no longer inert (the previous commit shipped the declaration with
nothing consuming it). Generated before register(), so a plugin declaring both gets
its generated page first, then its own; the page id is fixed so a plugin's own
registerSettingsPage cannot shadow it.
PluginSettingsPage wires the concurrency story end to end: the draft carries a
revision, every save sends it back as expectedRevision, and a stale save is rejected
by the server rather than silently clobbering another tab. The server's message is
surfaced verbatim because "does not match the schema" and "changed elsewhere" need
different actions from the user.
The form opens on incompatible stored data and says so, rather than refusing — that
is exactly when the user needs it to repair the values, and the stored data is
preserved until a valid save (Sol spec review MUST pingdotgg#13's recovery half).
This also makes the previously-vacuous test assertable. It now asserts the generated
page appears, and is verified deletion-sensitive: removing the synthesis fails both
new tests. `declared: false` (no schema, or plugin disabled) renders an explanation
rather than an empty form.
Gates: typecheck 0 errors; web 1319 passed; server 1672 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 16, 2026
Sol spec review MUST pingdotgg#13 (detection half). The store recorded a schema fingerprint
from the first commit but nothing ever read it back, so an upgrade that changed a
plugin's schema under already-stored values went undetected — the values were read
as if they still matched, which is exactly the silent misread the fingerprint exists
to prevent.
readDraft now surfaces the fingerprint that produced the stored values, and the
settings RPC compares it against the plugin's CURRENT schema. A mismatch marks the
draft incompatible, which the settings page already renders as "these settings need
attention" with the form open for repair.
The stored data is preserved on mismatch, not discarded: the user may need to read
the old values to reconstruct the new ones, and a plugin that briefly downgrades
should not lose its config. Recovery is a valid save, which rewrites both the values
and the fingerprint.
Tests verified deletion-sensitive: returning a null fingerprint from readDraft fails
both fingerprint tests.
Remaining for MUST pingdotgg#13: hostApi.settings.get should fail InvalidStored on drift
rather than relying on decode alone. The decode already fails for an incompatible
shape in the common case; the fingerprint makes it explicit. Tracked, not claimed.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 16, 2026
Sol spec review MUST pingdotgg#13, plugin-read half. hostApi.settings.get now rejects stored
values whose fingerprint does not match the plugin's current schema, instead of
relying on decode to notice.
Decode is not sufficient and that is the whole point: a widening change (adding an
optional field, relaxing a filter) still decodes cleanly, so the plugin would
silently run on values written for a shape it no longer declares. Comparing the
fingerprint makes the mismatch the fact being checked rather than a side effect of
decoding. The error carries no values — only the plugin id.
KNOWN GAP, stated rather than implied: this path has no test. I wrote one with a
fixture that probes settings.get from a service and records the Exit tag to its data
dir; the probe did not get scheduled within the test's wait, and the "<probe never
ran>" guard I built into it caught that rather than letting it pass vacuously against
whatever I asserted. Rather than ship a test that cannot reliably observe the
behaviour — or one that asserts only "the plugin activated", which would pass with
this check deleted — the tests are removed and the gap recorded.
Store-level fingerprint behaviour IS covered and verified deletion-sensitive
(PluginSettingsStore.test.ts); what is untested is specifically the capability read
path rejecting on drift. Needs a fixture harness that can await a forked service.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 16, 2026
Makes declarative settings real: a plugin declares a schema and the HOST renders the
form. The plugin ships no form, no RPC, no storage.
The loader now synthesises a settings page from `definition.settings`, so the
declaration is no longer inert (the previous commit shipped the declaration with
nothing consuming it). Generated before register(), so a plugin declaring both gets
its generated page first, then its own; the page id is fixed so a plugin's own
registerSettingsPage cannot shadow it.
PluginSettingsPage wires the concurrency story end to end: the draft carries a
revision, every save sends it back as expectedRevision, and a stale save is rejected
by the server rather than silently clobbering another tab. The server's message is
surfaced verbatim because "does not match the schema" and "changed elsewhere" need
different actions from the user.
The form opens on incompatible stored data and says so, rather than refusing — that
is exactly when the user needs it to repair the values, and the stored data is
preserved until a valid save (Sol spec review MUST pingdotgg#13's recovery half).
This also makes the previously-vacuous test assertable. It now asserts the generated
page appears, and is verified deletion-sensitive: removing the synthesis fails both
new tests. `declared: false` (no schema, or plugin disabled) renders an explanation
rather than an empty form.
Gates: typecheck 0 errors; web 1319 passed; server 1672 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 16, 2026
Sol spec review MUST pingdotgg#13 (detection half). The store recorded a schema fingerprint
from the first commit but nothing ever read it back, so an upgrade that changed a
plugin's schema under already-stored values went undetected — the values were read
as if they still matched, which is exactly the silent misread the fingerprint exists
to prevent.
readDraft now surfaces the fingerprint that produced the stored values, and the
settings RPC compares it against the plugin's CURRENT schema. A mismatch marks the
draft incompatible, which the settings page already renders as "these settings need
attention" with the form open for repair.
The stored data is preserved on mismatch, not discarded: the user may need to read
the old values to reconstruct the new ones, and a plugin that briefly downgrades
should not lose its config. Recovery is a valid save, which rewrites both the values
and the fingerprint.
Tests verified deletion-sensitive: returning a null fingerprint from readDraft fails
both fingerprint tests.
Remaining for MUST pingdotgg#13: hostApi.settings.get should fail InvalidStored on drift
rather than relying on decode alone. The decode already fails for an incompatible
shape in the common case; the fingerprint makes it explicit. Tracked, not claimed.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 16, 2026
Sol spec review MUST pingdotgg#13, plugin-read half. hostApi.settings.get now rejects stored
values whose fingerprint does not match the plugin's current schema, instead of
relying on decode to notice.
Decode is not sufficient and that is the whole point: a widening change (adding an
optional field, relaxing a filter) still decodes cleanly, so the plugin would
silently run on values written for a shape it no longer declares. Comparing the
fingerprint makes the mismatch the fact being checked rather than a side effect of
decoding. The error carries no values — only the plugin id.
KNOWN GAP, stated rather than implied: this path has no test. I wrote one with a
fixture that probes settings.get from a service and records the Exit tag to its data
dir; the probe did not get scheduled within the test's wait, and the "<probe never
ran>" guard I built into it caught that rather than letting it pass vacuously against
whatever I asserted. Rather than ship a test that cannot reliably observe the
behaviour — or one that asserts only "the plugin activated", which would pass with
this check deleted — the tests are removed and the gap recorded.
Store-level fingerprint behaviour IS covered and verified deletion-sensitive
(PluginSettingsStore.test.ts); what is untested is specifically the capability read
path rejecting on drift. Needs a fixture harness that can await a forked service.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 17, 2026
Makes declarative settings real: a plugin declares a schema and the HOST renders the
form. The plugin ships no form, no RPC, no storage.
The loader now synthesises a settings page from `definition.settings`, so the
declaration is no longer inert (the previous commit shipped the declaration with
nothing consuming it). Generated before register(), so a plugin declaring both gets
its generated page first, then its own; the page id is fixed so a plugin's own
registerSettingsPage cannot shadow it.
PluginSettingsPage wires the concurrency story end to end: the draft carries a
revision, every save sends it back as expectedRevision, and a stale save is rejected
by the server rather than silently clobbering another tab. The server's message is
surfaced verbatim because "does not match the schema" and "changed elsewhere" need
different actions from the user.
The form opens on incompatible stored data and says so, rather than refusing — that
is exactly when the user needs it to repair the values, and the stored data is
preserved until a valid save (Sol spec review MUST pingdotgg#13's recovery half).
This also makes the previously-vacuous test assertable. It now asserts the generated
page appears, and is verified deletion-sensitive: removing the synthesis fails both
new tests. `declared: false` (no schema, or plugin disabled) renders an explanation
rather than an empty form.
Gates: typecheck 0 errors; web 1319 passed; server 1672 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 17, 2026
Sol spec review MUST pingdotgg#13 (detection half). The store recorded a schema fingerprint
from the first commit but nothing ever read it back, so an upgrade that changed a
plugin's schema under already-stored values went undetected — the values were read
as if they still matched, which is exactly the silent misread the fingerprint exists
to prevent.
readDraft now surfaces the fingerprint that produced the stored values, and the
settings RPC compares it against the plugin's CURRENT schema. A mismatch marks the
draft incompatible, which the settings page already renders as "these settings need
attention" with the form open for repair.
The stored data is preserved on mismatch, not discarded: the user may need to read
the old values to reconstruct the new ones, and a plugin that briefly downgrades
should not lose its config. Recovery is a valid save, which rewrites both the values
and the fingerprint.
Tests verified deletion-sensitive: returning a null fingerprint from readDraft fails
both fingerprint tests.
Remaining for MUST pingdotgg#13: hostApi.settings.get should fail InvalidStored on drift
rather than relying on decode alone. The decode already fails for an incompatible
shape in the common case; the fingerprint makes it explicit. Tracked, not claimed.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 17, 2026
Sol spec review MUST pingdotgg#13, plugin-read half. hostApi.settings.get now rejects stored
values whose fingerprint does not match the plugin's current schema, instead of
relying on decode to notice.
Decode is not sufficient and that is the whole point: a widening change (adding an
optional field, relaxing a filter) still decodes cleanly, so the plugin would
silently run on values written for a shape it no longer declares. Comparing the
fingerprint makes the mismatch the fact being checked rather than a side effect of
decoding. The error carries no values — only the plugin id.
KNOWN GAP, stated rather than implied: this path has no test. I wrote one with a
fixture that probes settings.get from a service and records the Exit tag to its data
dir; the probe did not get scheduled within the test's wait, and the "<probe never
ran>" guard I built into it caught that rather than letting it pass vacuously against
whatever I asserted. Rather than ship a test that cannot reliably observe the
behaviour — or one that asserts only "the plugin activated", which would pass with
this check deleted — the tests are removed and the gap recorded.
Store-level fingerprint behaviour IS covered and verified deletion-sensitive
(PluginSettingsStore.test.ts); what is untested is specifically the capability read
path rejecting on drift. Needs a fixture harness that can await a forked service.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 30, 2026
Makes declarative settings real: a plugin declares a schema and the HOST renders the
form. The plugin ships no form, no RPC, no storage.
The loader now synthesises a settings page from `definition.settings`, so the
declaration is no longer inert (the previous commit shipped the declaration with
nothing consuming it). Generated before register(), so a plugin declaring both gets
its generated page first, then its own; the page id is fixed so a plugin's own
registerSettingsPage cannot shadow it.
PluginSettingsPage wires the concurrency story end to end: the draft carries a
revision, every save sends it back as expectedRevision, and a stale save is rejected
by the server rather than silently clobbering another tab. The server's message is
surfaced verbatim because "does not match the schema" and "changed elsewhere" need
different actions from the user.
The form opens on incompatible stored data and says so, rather than refusing — that
is exactly when the user needs it to repair the values, and the stored data is
preserved until a valid save (Sol spec review MUST pingdotgg#13's recovery half).
This also makes the previously-vacuous test assertable. It now asserts the generated
page appears, and is verified deletion-sensitive: removing the synthesis fails both
new tests. `declared: false` (no schema, or plugin disabled) renders an explanation
rather than an empty form.
Gates: typecheck 0 errors; web 1319 passed; server 1672 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 30, 2026
Sol spec review MUST pingdotgg#13 (detection half). The store recorded a schema fingerprint
from the first commit but nothing ever read it back, so an upgrade that changed a
plugin's schema under already-stored values went undetected — the values were read
as if they still matched, which is exactly the silent misread the fingerprint exists
to prevent.
readDraft now surfaces the fingerprint that produced the stored values, and the
settings RPC compares it against the plugin's CURRENT schema. A mismatch marks the
draft incompatible, which the settings page already renders as "these settings need
attention" with the form open for repair.
The stored data is preserved on mismatch, not discarded: the user may need to read
the old values to reconstruct the new ones, and a plugin that briefly downgrades
should not lose its config. Recovery is a valid save, which rewrites both the values
and the fingerprint.
Tests verified deletion-sensitive: returning a null fingerprint from readDraft fails
both fingerprint tests.
Remaining for MUST pingdotgg#13: hostApi.settings.get should fail InvalidStored on drift
rather than relying on decode alone. The decode already fails for an incompatible
shape in the common case; the fingerprint makes it explicit. Tracked, not claimed.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 30, 2026
Sol spec review MUST pingdotgg#13, plugin-read half. hostApi.settings.get now rejects stored
values whose fingerprint does not match the plugin's current schema, instead of
relying on decode to notice.
Decode is not sufficient and that is the whole point: a widening change (adding an
optional field, relaxing a filter) still decodes cleanly, so the plugin would
silently run on values written for a shape it no longer declares. Comparing the
fingerprint makes the mismatch the fact being checked rather than a side effect of
decoding. The error carries no values — only the plugin id.
KNOWN GAP, stated rather than implied: this path has no test. I wrote one with a
fixture that probes settings.get from a service and records the Exit tag to its data
dir; the probe did not get scheduled within the test's wait, and the "<probe never
ran>" guard I built into it caught that rather than letting it pass vacuously against
whatever I asserted. Rather than ship a test that cannot reliably observe the
behaviour — or one that asserts only "the plugin activated", which would pass with
this check deleted — the tests are removed and the gap recorded.
Store-level fingerprint behaviour IS covered and verified deletion-sensitive
(PluginSettingsStore.test.ts); what is untested is specifically the capability read
path rejecting on drift. Needs a fixture harness that can await a forked service.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 30, 2026
Makes declarative settings real: a plugin declares a schema and the HOST renders the
form. The plugin ships no form, no RPC, no storage.
The loader now synthesises a settings page from `definition.settings`, so the
declaration is no longer inert (the previous commit shipped the declaration with
nothing consuming it). Generated before register(), so a plugin declaring both gets
its generated page first, then its own; the page id is fixed so a plugin's own
registerSettingsPage cannot shadow it.
PluginSettingsPage wires the concurrency story end to end: the draft carries a
revision, every save sends it back as expectedRevision, and a stale save is rejected
by the server rather than silently clobbering another tab. The server's message is
surfaced verbatim because "does not match the schema" and "changed elsewhere" need
different actions from the user.
The form opens on incompatible stored data and says so, rather than refusing — that
is exactly when the user needs it to repair the values, and the stored data is
preserved until a valid save (Sol spec review MUST pingdotgg#13's recovery half).
This also makes the previously-vacuous test assertable. It now asserts the generated
page appears, and is verified deletion-sensitive: removing the synthesis fails both
new tests. `declared: false` (no schema, or plugin disabled) renders an explanation
rather than an empty form.
Gates: typecheck 0 errors; web 1319 passed; server 1672 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 30, 2026
Sol spec review MUST pingdotgg#13 (detection half). The store recorded a schema fingerprint
from the first commit but nothing ever read it back, so an upgrade that changed a
plugin's schema under already-stored values went undetected — the values were read
as if they still matched, which is exactly the silent misread the fingerprint exists
to prevent.
readDraft now surfaces the fingerprint that produced the stored values, and the
settings RPC compares it against the plugin's CURRENT schema. A mismatch marks the
draft incompatible, which the settings page already renders as "these settings need
attention" with the form open for repair.
The stored data is preserved on mismatch, not discarded: the user may need to read
the old values to reconstruct the new ones, and a plugin that briefly downgrades
should not lose its config. Recovery is a valid save, which rewrites both the values
and the fingerprint.
Tests verified deletion-sensitive: returning a null fingerprint from readDraft fails
both fingerprint tests.
Remaining for MUST pingdotgg#13: hostApi.settings.get should fail InvalidStored on drift
rather than relying on decode alone. The decode already fails for an incompatible
shape in the common case; the fingerprint makes it explicit. Tracked, not claimed.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 30, 2026
Sol spec review MUST pingdotgg#13, plugin-read half. hostApi.settings.get now rejects stored
values whose fingerprint does not match the plugin's current schema, instead of
relying on decode to notice.
Decode is not sufficient and that is the whole point: a widening change (adding an
optional field, relaxing a filter) still decodes cleanly, so the plugin would
silently run on values written for a shape it no longer declares. Comparing the
fingerprint makes the mismatch the fact being checked rather than a side effect of
decoding. The error carries no values — only the plugin id.
KNOWN GAP, stated rather than implied: this path has no test. I wrote one with a
fixture that probes settings.get from a service and records the Exit tag to its data
dir; the probe did not get scheduled within the test's wait, and the "<probe never
ran>" guard I built into it caught that rather than letting it pass vacuously against
whatever I asserted. Rather than ship a test that cannot reliably observe the
behaviour — or one that asserts only "the plugin activated", which would pass with
this check deleted — the tests are removed and the gap recorded.
Store-level fingerprint behaviour IS covered and verified deletion-sensitive
(PluginSettingsStore.test.ts); what is untested is specifically the capability read
path rejecting on drift. Needs a fixture harness that can await a forked service.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 31, 2026
Makes declarative settings real: a plugin declares a schema and the HOST renders the
form. The plugin ships no form, no RPC, no storage.
The loader now synthesises a settings page from `definition.settings`, so the
declaration is no longer inert (the previous commit shipped the declaration with
nothing consuming it). Generated before register(), so a plugin declaring both gets
its generated page first, then its own; the page id is fixed so a plugin's own
registerSettingsPage cannot shadow it.
PluginSettingsPage wires the concurrency story end to end: the draft carries a
revision, every save sends it back as expectedRevision, and a stale save is rejected
by the server rather than silently clobbering another tab. The server's message is
surfaced verbatim because "does not match the schema" and "changed elsewhere" need
different actions from the user.
The form opens on incompatible stored data and says so, rather than refusing — that
is exactly when the user needs it to repair the values, and the stored data is
preserved until a valid save (Sol spec review MUST pingdotgg#13's recovery half).
This also makes the previously-vacuous test assertable. It now asserts the generated
page appears, and is verified deletion-sensitive: removing the synthesis fails both
new tests. `declared: false` (no schema, or plugin disabled) renders an explanation
rather than an empty form.
Gates: typecheck 0 errors; web 1319 passed; server 1672 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 31, 2026
Sol spec review MUST pingdotgg#13 (detection half). The store recorded a schema fingerprint
from the first commit but nothing ever read it back, so an upgrade that changed a
plugin's schema under already-stored values went undetected — the values were read
as if they still matched, which is exactly the silent misread the fingerprint exists
to prevent.
readDraft now surfaces the fingerprint that produced the stored values, and the
settings RPC compares it against the plugin's CURRENT schema. A mismatch marks the
draft incompatible, which the settings page already renders as "these settings need
attention" with the form open for repair.
The stored data is preserved on mismatch, not discarded: the user may need to read
the old values to reconstruct the new ones, and a plugin that briefly downgrades
should not lose its config. Recovery is a valid save, which rewrites both the values
and the fingerprint.
Tests verified deletion-sensitive: returning a null fingerprint from readDraft fails
both fingerprint tests.
Remaining for MUST pingdotgg#13: hostApi.settings.get should fail InvalidStored on drift
rather than relying on decode alone. The decode already fails for an incompatible
shape in the common case; the fingerprint makes it explicit. Tracked, not claimed.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 31, 2026
Sol spec review MUST pingdotgg#13, plugin-read half. hostApi.settings.get now rejects stored
values whose fingerprint does not match the plugin's current schema, instead of
relying on decode to notice.
Decode is not sufficient and that is the whole point: a widening change (adding an
optional field, relaxing a filter) still decodes cleanly, so the plugin would
silently run on values written for a shape it no longer declares. Comparing the
fingerprint makes the mismatch the fact being checked rather than a side effect of
decoding. The error carries no values — only the plugin id.
KNOWN GAP, stated rather than implied: this path has no test. I wrote one with a
fixture that probes settings.get from a service and records the Exit tag to its data
dir; the probe did not get scheduled within the test's wait, and the "<probe never
ran>" guard I built into it caught that rather than letting it pass vacuously against
whatever I asserted. Rather than ship a test that cannot reliably observe the
behaviour — or one that asserts only "the plugin activated", which would pass with
this check deleted — the tests are removed and the gap recorded.
Store-level fingerprint behaviour IS covered and verified deletion-sensitive
(PluginSettingsStore.test.ts); what is untested is specifically the capability read
path rejecting on drift. Needs a fixture harness that can await a forked service.
Gates: typecheck 0 errors; server 1675 passed.
aorwall added a commit to aorwall/t3code that referenced this pull request Aug 2, 2026
* docs: spec for preview servers in the T3 Code web app
Approved spec covering the Servers right-panel view, Moatless-owned preview
tabs backed by Redis, and an iframe renderer that lets the existing browser
panel work outside Electron.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat: add the servers.* contract group
Three reads over the servers a thread's environment declares: list them, watch
their status, and follow one's log. Field names are taken verbatim from the
host's own server record so the server side is a rename-free serialization.
Nothing here starts, stops or reconfigures a server; those methods are
specified elsewhere and deliberately out of scope.
The preview module docstring said the preview is desktop-only and that the
desktop renderer mediates. Both stop being true in the same change, so the
docstring now describes the surface by capability rather than by client.
* feat: carry the servers.* group through the client runtime and reference server
Client-side atoms for the three reads, with the log subscription folding
lines into a bounded buffer so a remounted panel keeps what it received
rather than starting over.
The reference server declares no thread servers - it runs threads on the
machine it is on - so it answers the list empty and both subscriptions
silent. A hosted environment answers the same three methods with real data.
Also splits the preview runtime capability in two. One boolean was answering
both "can this runtime show a page" and "can this runtime read the page it
shows", which are the same question only on the desktop app.
* feat: host preview pages in a sandboxed frame on the web
The web app could not show a task's page at all. It can now: one frame per
open tab, mounted at the app root so it survives the panel collapsing, and
positioned by the same surface rect the desktop webview uses.
The frame is driven and never read. Navigation writes to the server and the
frame follows; refresh replaces the element because a cross-origin frame has
no reload and reassigning src would grow the parent's history; back and
forward are absent rather than dead.
Two things a frame cannot report, the panel says from elsewhere. A page that
is not there is explained by what its server says about itself, which is
better than a net error - "installing" is an answer no net error carries. A
page that renders nothing while its server says it is running gets a hint
offered as a hint, because a frame-ancestors refusal and the preview host's
own 401 look identical from outside and neither fires an event.
* feat: add a Servers view to the right panel
One row per server the thread's environment declares, with its status kept
current by the subscription, its log on demand, and an Open that hands the
URL to a browser tab.
Everything in it is a read - a row that says failed offers its log and no
button. Restarting a server is a write and lands elsewhere.
The right panel's persisted state moves to version 8 for the new surface
kind, and its migration now drops surfaces whose kind this build does not
know. That is what makes the version bump safe to downgrade away from: the
rest of the workspace survives and only the unknown tab is lost.
* test: cover the browser preview surface and the servers view
Two products share these schemas and no test process, so the seam is a set
of real Moatless responses checked in here and decoded by the schemas
themselves. When its projection changes the fixture changes with it in one
commit, and the decode test is what fails if the two drift.
The rest covers what the change actually promises: the capability answers
three runtimes, the chrome row omits controls rather than disabling them,
the frame re-keys instead of reassigning src, exactly one browser host
renders, and the panel state survives the version bump while dropping a kind
this build does not know.
* fix: state the environment's absence rather than implying it from a row
The never-provisioned fixture claimed a server is listed as `stopped` with
no URL. It is not. Moatless resolves status config-first from a NotFound
pod, which falls back to `starting` with the ingress URL the port will have
— so the panel showed `starting` forever for an environment that does not
exist, and offered an Open button pointing at a 502.
The fixture now carries what the backend produces, and the panel states the
environment's own status above the list instead of leaving it to be inferred
from rows that cannot say it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: track the fork's own delta, not just what upstream does to it
The policy could answer "who wins this conflict" but not "what did we change",
and the second is the question you are actually asking when git stops on a line
you do not recognise. The path table only ever listed files we expected to fight
over — a much smaller set than the files we changed. The whole thread-servers
group, around forty files, appeared nowhere.
§3 is that missing list, and a fifth hazard in the preamble names the failure it
prevents: a conflict resolved toward upstream because nobody could tell our line
was deliberate. Two consequences: the checklist asks for a row in the same commit
as the change rather than at the next merge, and reading the inventory is now a
step in resolving a conflict rather than something to remember.
Also adds path-policy rows for the highest-risk of those files —
`apps/server/src/ws.ts` and `RpcAuthorization.ts`, the only upstream server files
the fork touches, where the resolution is to take theirs and re-add three
`servers.*` entries.
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
aorwall pushed a commit to aorwall/t3code that referenced this pull request Aug 12, 2026
The 2026-08-08 upstream merge took 89 minutes. Most of that was not conflict
resolution; it was performing checks that existed only as prose in
docs/fork/upstream-merge-inventory.md, one at a time, and rediscovering two
things the doc could have told me before I started.
Five changes, in the order they pay off.
**The inventory is data now.** docs/fork/inventory.json holds the fork-owned
concerns, path policy, fork inventory, tripwires, deliberately deleted upstream
paths, off-repository state and convergence entries. The markdown keeps only
what a check cannot hold — the four re-application deltas, where the
unsupported-method set comes from, and the reasoning behind the path policy
rule — and drops from 784 lines to 243.
**The checks run.** Five dependency-free scripts under the skill:
- `preflight.mjs` — the range, stale entries, the owned-concern sweep, and the
conflict forecast: every file both sides touched, grouped by the verdict that
resolves it, before the merge starts.
- `inventory-check.mjs` — every inventory path read back out of upstream/main.
This is the one that matters: last merge, upstream moved SidebarV2's content
into Sidebar.tsx and Sidebar.tsx's into LegacySidebar.tsx. Git cannot see a
content swap as a rename — it is a delete paired with a modify, which no -M
threshold detects — so it surfaced as a modify/delete conflict mid-merge.
Run against the pre-merge tree, this check names it in about a second.
- `tripwires.mjs`, `unsupported-methods.mjs`, `verify.mjs`.
Running inventory-check against the current tree immediately found three dead
path-policy entries that had been faithfully transcribed forward through
several merges, for directories deleted in pingdotgg#13 and pingdotgg#26.
**Verification is one command, earlier.** `verify.mjs` runs tripwires, the
unsupported-method derivation, format, lint, types and tests — and does not
stop at the first failure, so a formatting nit no longer hides the type errors
behind it. It raises the heap the web suite needs, whose failure mode is
otherwise an exit 137 that reads like a real test failure. The skill now runs
it *before* the documentation steps, because its output is their input.
**Counts come out of prose.** Both snapshots the last merge relied on were
stale. "38 of 87 methods" and "Clerk 4 / pairing 73 / session bootstrap 9" are
replaced by the command that derives them.
**The fork test no longer hard-codes upstream paths.** features.test.ts read
`../components/Sidebar.tsx?raw`; when upstream renamed that file the whole
suite failed to build, at a module path, saying nothing about the gate. It now
reads the guarded files out of inventory.json and reaches them through
import.meta.glob, so a rename fails as a named assertion that says which entry
to re-point — and the test and the merge scripts read the same guards, so they
cannot drift apart.
Verified: fmt, lint, typecheck and 1908 tests pass. `tripwires.mjs` reports one
finding, which is genuine and already tracked — thread-transfer-report.yml went
active on GitHub when the merge branch was pushed and still needs disabling.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
NeilTheFisher pushed a commit to NeilTheFisher/t3code that referenced this pull request Aug 18, 2026
AFLabAI added a commit to AFLabAI/t3code that referenced this pull request Sep 2, 2026
Increase diagnostic visibility by appending complete vp check stdout/stderr to job summary instead of truncating. This exposes all 11 lint errors detected in RUN pingdotgg#13.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Sign up for freeto 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

@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: Commit, Push and Create PR actions - #13

Merged
juliusmarminge merged 18 commits into
mainfrom
codex/add-github-commit-push-ui
Feb 12, 2026
Merged

feat: Commit, Push and Create PR actions#13
juliusmarminge merged 18 commits into
mainfrom
codex/add-github-commit-push-ui

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Feb 12, 2026

Copy link
Copy Markdown
Member

Open with Devin

Summary by CodeRabbit

  • New Features

    • AI-powered generation for commit messages and PR title/body.
    • New Git core & manager with stacked workflows (commit, commit & push, commit & push & create/open PR), richer status details, and WebSocket/native endpoints to run Git status and actions.
    • UI: Git actions menu with real-time status, action execution, notices and error handling.
  • Chores

    • Robust process runner with buffer and timeout safeguards.
  • Tests

    • Extensive end-to-end and unit tests covering Git flows, PR lifecycle, manager behavior, and contract schemas.

@coderabbitai

coderabbitaiBot commented Feb 12, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds a Git subsystem: process runner with buffer/timeouts, Git core service, GitManager orchestration, Codex-based commit/PR generation, WS API + server wiring, client UI integration, contracts/schemas for stacked git actions, and extensive tests.

Changes

Cohort / File(s)Summary
Text Generation Service
apps/server/src/coreServices.ts, apps/server/src/codexTextGenerator.ts
Defines TextGenerationService types and implements CodexTextGenerator with JSON schemas, validation/parsers, temp-file helpers, runCodexJson orchestration, and methods to generate commit messages and PR content.
Process Execution
apps/server/src/processRunner.ts
Adds runProcess with configurable maxBufferBytes, stdout/stderr accumulation and byte limits, timeout handling (SIGTERM→SIGKILL), spawn/exit normalization, and detailed error types.
Git Core Service
apps/server/src/git.ts
Introduces GitCoreService and helpers: enriched status/statusDetails, prepareCommitContext, commit, pushCurrentBranch, readRangeContext, readConfigValue, runGit helpers, and upstream/default-branch logic.
GitManager Orchestration
apps/server/src/gitManager.ts
Adds GitManager coordinating gitCore, processRunner, Codex text generation, and gh interactions for stacked actions (commit, push, PR), PR discovery/creation, temp-file PR bodies, and error normalization.
Server WS Integration & Tests
apps/server/src/wsServer.ts, apps/server/src/wsServer.test.ts
Wires GitManager into server options, exposes WS methods git.status and git.runStackedAction, and updates tests to inject/validate gitManager delegation and error propagation.
Tests: GitManager & GitCore
apps/server/src/gitManager.test.ts, apps/server/src/git.test.ts
Adds end-to-end tests covering stacked actions, GH CLI scenarios, upstream behavior, detached HEAD, auth errors, and GitCoreService behaviors.
Client Integration
apps/web/src/wsNativeApi.ts, apps/web/src/components/ChatView.tsx
Extends native API with git.status and git.runStackedAction; integrates Git actions menu and UI state into ChatView with status polling, action execution, and notices.
Contracts / IPC / WS Methods & Tests
packages/contracts/src/git.ts, packages/contracts/src/git.test.ts, packages/contracts/src/ipc.ts, packages/contracts/src/ws.ts
Adds Zod schemas/types for git status and stacked actions, updates NativeApi.git signatures, expands WS_METHODS with git.status and git.runStackedAction, and adds schema tests.

Sequence Diagram(s)

sequenceDiagram
participant Client as Client (ChatView)
participant WS as WebSocket Server
participant GM as GitManager
participant GC as GitCoreService
participant Proc as ProcessRunner
participant Codex as Codex Service
participant GH as GitHub CLI
Client->>WS: git.runStackedAction(action, cwd)
WS->>GM: runStackedAction()
rect rgba(100,150,200,0.5)
Note over GM: Commit Step
GM->>GC: prepareCommitContext(cwd)
GC-->>GM: stagedSummary, stagedPatch
GM->>Codex: generateCommitMessage(diff)
Codex-->>GM: subject, body
GM->>Proc: git commit -m "..."
Proc-->>GM: commit result
end
rect rgba(100,200,150,0.5)
Note over GM: Push Step (if requested)
GM->>GC: pushCurrentBranch(cwd, upstream?)
GC-->>GM: push result
end
rect rgba(200,150,100,0.5)
Note over GM: PR Step (if requested)
GM->>GC: readRangeContext(base, head)
GC-->>GM: commitSummary, diffSummary, diffPatch
GM->>Codex: generatePrContent(rangeContext)
Codex-->>GM: title, body
GM->>GH: gh pr list --head branch
GH-->>GM: existing PRs
alt PR exists
GM->>GH: gh pr view PR_NUMBER
GH-->>GM: PR details
else
GM->>GH: gh pr create --title "..." --body file://tmp
GH-->>GM: new PR info
end
end
GM-->>WS: GitRunStackedActionResult
WS-->>Client: result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 2.56% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title 'feat: Commit, Push and Create PR actions' accurately captures the main feature added: three new Git workflow actions integrated into the UI and backend services.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch codex/add-github-commit-push-ui

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps

greptile-appsBot commented Feb 12, 2026

Copy link
Copy Markdown

Greptile Overview

Greptile Summary

This PR implements comprehensive Git stacked actions (commit, push, create PR) with AI-powered text generation for commit messages and PR content. The implementation spans server-side orchestration, robust process execution, WebSocket/IPC integration, and a polished React UI with real-time progress tracking.

Key changes:

  • New GitManager orchestrates stacked workflows (commit → push → PR) with proper error handling and GitHub CLI integration
  • GitCoreService provides low-level Git operations using direct spawn (no shell), with buffer management and timeout safeguards
  • CodexTextGenerator generates commit messages and PR content via Codex CLI with structured JSON output schemas
  • ProcessRunner implements robust subprocess execution with truncation support, graceful termination, and buffer limit enforcement
  • GitActionsControl UI component provides modal-based workflow with step-by-step progress, custom commit messages, and error states
  • Comprehensive test coverage across unit tests (git.test.ts, processRunner.test.ts) and integration tests (gitManager.test.ts)
  • WebSocket and IPC layers extended to expose git.status and git.runStackedAction methods to both web and desktop clients

Architecture aligns with project priorities:

  • Performance: Direct spawn without shell overhead, buffer limits prevent memory issues
  • Reliability: Proper timeout handling, graceful degradation (PR lookup is best-effort), temp file cleanup
  • Predictable behavior: Structured status tracking, deterministic base branch resolution, comprehensive error normalization

Confidence Score: 5/5

  • This PR is safe to merge with high confidence
  • The implementation demonstrates strong engineering discipline: comprehensive test coverage (490 lines in gitManager.test.ts alone), proper error handling with normalized error messages, robust subprocess management with buffer limits and timeouts, dependency injection for testability, and alignment with project priorities (performance, reliability, predictable behavior). The only noted issue is duplicate commit message sanitization which is cosmetic and doesn't affect functionality.
  • No files require special attention

Important Files Changed

FilenameOverview
apps/server/src/git.tsAdds comprehensive Git operations with robust error handling, proper spawn arguments, and buffer management for status, commit, push, and range context operations
apps/server/src/gitManager.tsImplements high-level orchestration for stacked Git workflows (commit, push, PR) with AI text generation, proper dependency injection, and GitHub CLI integration
apps/server/src/codexTextGenerator.tsImplements AI-powered text generation for commit messages and PR content using Codex CLI with structured JSON output, proper sanitization, and temp file cleanup
apps/server/src/processRunner.tsAdds robust process execution with buffer limit enforcement, timeout handling, graceful termination (SIGTERM then SIGKILL), and truncation support
apps/server/src/wsServer.tsIntegrates GitManager into WebSocket server to expose git.status and git.runStackedAction methods via WS protocol
packages/contracts/src/git.tsExtends Git contracts with Zod schemas for stacked action API (status, commit, push, PR) with comprehensive validation rules
apps/web/src/components/GitActionsControl.tsxImplements comprehensive Git actions UI with modal workflow, real-time progress tracking, step-by-step execution, error handling, and PR link opening

Sequence Diagram

sequenceDiagram
participant User
participant GitActionsControl
participant NativeApi
participant GitManager
participant GitCore
participant CodexTextGenerator
participant GitCLI
participant GitHubCLI
User->>GitActionsControl: Click "Commit and create PR"
GitActionsControl->>GitActionsControl: Open modal, set action
User->>GitActionsControl: Confirm action
GitActionsControl->>NativeApi: git.runStackedAction(commit)
NativeApi->>GitManager: runStackedAction(commit)
GitManager->>GitCore: statusDetails(cwd)
GitCore->>GitCLI: git status --porcelain=2
GitCLI-->>GitCore: status output
GitCore-->>GitManager: branch, upstream info
GitManager->>GitCore: prepareCommitContext(cwd)
GitCore->>GitCLI: git add -A
GitCore->>GitCLI: git diff --cached
GitCLI-->>GitCore: staged changes
GitCore-->>GitManager: stagedSummary, stagedPatch
GitManager->>CodexTextGenerator: generateCommitMessage()
CodexTextGenerator->>CodexTextGenerator: Write temp schema file
CodexTextGenerator->>GitCLI: codex exec --output-schema
GitCLI-->>CodexTextGenerator: JSON response
CodexTextGenerator->>CodexTextGenerator: Parse, sanitize, cleanup
CodexTextGenerator-->>GitManager: {subject, body}
GitManager->>GitCore: commit(cwd, subject, body)
GitCore->>GitCLI: git commit -m subject -m body
GitCLI-->>GitCore: success
GitCore-->>GitManager: {commitSha}
GitManager-->>NativeApi: commit result
NativeApi-->>GitActionsControl: Update progress: commit completed
GitActionsControl->>NativeApi: git.runStackedAction(commit_push)
NativeApi->>GitManager: runStackedAction(commit_push)
GitManager->>GitCore: pushCurrentBranch(cwd)
GitCore->>GitCLI: git push -u origin branch
GitCLI-->>GitCore: success
GitCore-->>GitManager: {status: pushed, branch}
GitManager-->>NativeApi: push result
NativeApi-->>GitActionsControl: Update progress: push completed
GitActionsControl->>NativeApi: git.runStackedAction(commit_push_pr)
NativeApi->>GitManager: runStackedAction(commit_push_pr)
GitManager->>GitHubCLI: gh pr list --head branch
GitHubCLI-->>GitManager: [] (no existing PR)
GitManager->>GitCore: readRangeContext(cwd, baseBranch)
GitCore->>GitCLI: git log, git diff
GitCLI-->>GitCore: commit history, diff
GitCore-->>GitManager: rangeContext
GitManager->>CodexTextGenerator: generatePrContent()
CodexTextGenerator->>GitCLI: codex exec --output-schema
GitCLI-->>CodexTextGenerator: JSON response
CodexTextGenerator-->>GitManager: {title, body}
GitManager->>GitHubCLI: gh pr create --title --body-file
GitHubCLI-->>GitManager: PR URL
GitManager->>GitHubCLI: gh pr view --web
GitHubCLI-->>GitManager: success
GitManager-->>NativeApi: pr result
NativeApi-->>GitActionsControl: Update progress: PR created
GitActionsControl->>User: Show completion with PR link
Loading

Last reviewed commit: dd92084

@greptile-appsgreptile-appsBot 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.

14 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment threadapps/server/src/gitManager.ts

@coderabbitaicoderabbitaiBot 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.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@apps/server/src/processRunner.ts`:
- Around line 128-131: Attach an 'error' listener to the child's stdin and use
the write callback to handle possible write errors before calling end: in
processRunner.ts, before calling child.stdin.write(...) register
child.stdin.once("error", err => {/* reject/forward error or cleanup and ensure
promise rejects */}), call child.stdin.write(options.stdin, (err) => { if (err)
{ /* handle/reject/cleanup */ } child.stdin.end(); }); and remove the
unconditional child.stdin.end() so we only end after the write completes; ensure
any error handling forwards the error to the same rejection/cleanup path used by
child.once("error") for the spawned process.
🧹 Nitpick comments (2)
packages/contracts/src/git.test.ts (1)

11-71: LGTM!

The tests provide good coverage for schema validation, including whitespace trimming and nested field parsing.

Consider adding negative test cases to verify that invalid inputs are rejected (e.g., invalid action strings, missing required fields). This would strengthen the contract validation.

,

apps/server/src/codexTextGenerator.ts (1)

84-103: Consider consolidating duplicate sanitization logic.

sanitizeCommitSubject here (lines 84-95) and sanitizeCommitMessage in gitManager.ts (lines 99-110) perform nearly identical operations: extracting the first line, removing trailing periods, and truncating to 72 characters. This duplication could lead to divergent behavior over time.

Consider extracting a shared utility or having gitManager.ts rely on the already-sanitized output from CodexTextGenerator without additional sanitization.

Comment threadapps/server/src/processRunner.ts
Comment threadapps/server/src/gitManager.ts Outdated
Comment threadapps/server/src/processRunner.ts
Comment threadapps/server/src/processRunner.ts
Comment threadapps/server/src/wsServer.ts
Comment threadapps/server/src/gitManager.ts Outdated
Comment threadapps/web/src/components/ChatView.tsx Outdated
Comment threadapps/server/src/codexTextGenerator.ts Outdated
@macroscopeapp

macroscopeappBot commented Feb 12, 2026

Copy link
Copy Markdown
Contributor

Add Commit, Push, and Create PR actions by introducing GitCoreService, GitManager.runStackedAction, and a web GitActionsControl wired through WS and IPC APIs

Implements stacked git workflows across server and web: adds GitCoreService for git operations, CodexTextGenerator for commit/PR text, and GitManager orchestration; exposes git.status and git.runStackedAction over WebSocket; adds desktop bridge shell.openExternal; and introduces a GitActionsControl UI to run commit/push/PR. Terminal spawning gains multi-shell fallback and runProcess provides standardized subprocess handling. See apps/server/src/gitManager.ts, apps/server/src/git.ts, apps/server/src/codexTextGenerator.ts, and apps/web/src/components/GitActionsControl.tsx.

📍Where to Start

Start with the orchestration entrypoint GitManager.runStackedAction in apps/server/src/gitManager.ts, then review GitCoreService in apps/server/src/git.ts and the web client GitActionsControl in apps/web/src/components/GitActionsControl.tsx.


Macroscope summarized dd92084.

Co-authored-by: codex <codex@users.noreply.github.com>
Comment threadapps/server/src/gitManager.ts

@coderabbitaicoderabbitaiBot 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.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@apps/web/src/components/ChatView.tsx`:
- Around line 395-424: When a git status fetch succeeds we need to clear any
previous error so the error banner doesn't persist; inside the load async
function (the one that calls api.git.status with gitCwd) after successfully
calling setGitStatus(nextStatus) also call setGitActionError(null) (guarded by
the same !cancelled check) so successful refreshes remove stale errors; update
the useEffect's load success branch in ChatView.tsx (the load function /
useEffect that references api, gitCwd, setGitStatus, setGitActionError)
accordingly.

Comment threadapps/web/src/components/ChatView.tsx Outdated
Comment threadapps/server/src/git.ts
Comment threadapps/server/src/git.ts Outdated

@coderabbitaicoderabbitaiBot 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.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@apps/server/src/git.test.ts`:
- Around line 460-466: The test invokes git with a single-quoted remote path
which fails on Windows; update the call that runs git(tmp.path, `remote add
origin '${remote.path}'`) to wrap the path using JSON.stringify(remote.path)
(i.e., produce a double-quoted, escaped string) so Windows cmd handles spaces
correctly; modify the test where makeTmpDir()/remote and the git(...) invocation
are used (see git(tmp.path, `remote add origin ...`), initRepoWithCommit,
createGitBranch) to pass the JSON.stringify-wrapped path instead of single
quotes.
In `@apps/server/src/git.ts`:
- Around line 245-251: The current early-return treats a branch as
"skipped_up_to_date" when details.hasUpstream && details.aheadCount === 0 even
if it is behind; update the condition to also require details.behindCount === 0
so we only mark truly up-to-date branches. Change the if that checks
details.hasUpstream and details.aheadCount to: details.hasUpstream &&
details.aheadCount === 0 && details.behindCount === 0 (leaving the returned
object with branch and optional upstreamBranch unchanged).
- Around line 129-143: The timeout check must be unconditional: in
runGitOrThrow, always throw when result.timedOut by calling
normalizeGitExecutionError(args, result) (or similar) before considering
options.allowNonZeroExit; then keep the existing non-zero exit handling for
result.code when options.allowNonZeroExit is false. Update the logic in
runGitOrThrow (referencing runGitOrThrow, RunGitOptions,
options.allowNonZeroExit, result.timedOut, result.code, and
normalizeGitExecutionError) so timeouts are detected and thrown unconditionally
while allowing suppressed non-zero exit codes only when appropriate.
In `@apps/server/src/gitManager.ts`:
- Around line 402-423: Both runGh and runGhStdout currently call this.run("gh",
args, { cwd }) and can hang; add an explicit timeoutMs option to those calls.
Define a clear constant (e.g. GH_CLI_TIMEOUT_MS = 30_000) near the top of the
module and pass it into this.run as { cwd, timeoutMs: GH_CLI_TIMEOUT_MS } in
both runGh and runGhStdout so gh CLI invocations time out predictably. Ensure
the constant is used in both functions and adjust any types if needed to match
ProcessRunOptions.
🧹 Nitpick comments (1)
apps/server/src/gitManager.ts (1)

175-183: If no commit is created, skip push/PR to avoid empty actions.

When runCommitStep returns skipped_no_changes, the current flow still pushes and can attempt PR creation. That can create confusing “no‑op” PRs or unnecessary network calls.

Comment threadapps/server/src/git.test.ts
Comment threadapps/server/src/git.ts
Comment threadapps/server/src/git.ts Outdated
Comment threadapps/server/src/gitManager.ts
@juliusmarmingejuliusmarminge changed the title Add stacked GitHub action workflowGithubFeb 12, 2026
@juliusmarmingejuliusmarminge changed the title Githubfeat: Commit, Push and Create PR actionsFeb 12, 2026
Co-authored-by: codex <codex@users.noreply.github.com>
Comment on lines +123 to +128
const fail = (error: Error): void => {
child.kill("SIGTERM");
finalize(() => {
reject(error);
});
};

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.

🟡 Medium

src/processRunner.ts:123 The fail function clears the timeout that would escalate to SIGKILL, so if the child ignores SIGTERM, the parent hangs forever. Consider adding a force-kill timer in fail similar to the timeout logic (lines 108-110).

- const fail = (error: Error): void => {- child.kill("SIGTERM");- finalize(() => {- reject(error);- });+ const fail = (error: Error): void => {+ child.kill("SIGTERM");+ const killTimer = setTimeout(() => {+ child.kill("SIGKILL");+ }, 1_000);+ finalize(() => {+ clearTimeout(killTimer);+ reject(error);+ });

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/processRunner.ts around lines 123-128:
The `fail` function clears the timeout that would escalate to `SIGKILL`, so if the child ignores `SIGTERM`, the parent hangs forever. Consider adding a force-kill timer in `fail` similar to the timeout logic (lines 108-110).

Comment threadapps/server/src/processRunner.ts Outdated
stream: "stdout" | "stderr",
chunk: Buffer | string,
): Error | null => {
const text = chunk.toString();

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.

🟢 Low

src/processRunner.ts:134 Using chunk.toString() per Buffer can split multi‑byte UTF‑8 and corrupt text; consider StringDecoder to assemble stdout/stderr safely.

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/processRunner.ts around line 134:
Using `chunk.toString()` per `Buffer` can split multi‑byte UTF‑8 and corrupt text; consider `StringDecoder` to assemble `stdout`/`stderr` safely.

@coderabbitaicoderabbitaiBot 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/server/src/git.ts (1)

50-86: ⚠️ Potential issue | 🟠 Major

Add output caps to avoid unbounded git command buffering.
runGit accumulates stdout/stderr without limits; large diffs can spike memory and make behavior unpredictable under load. Consider enforcing a max buffer (similar to processRunner) or streaming to disk with a safe cap.

🔧 Suggested fix (bounded output)
+const DEFAULT_GIT_MAX_BUFFER_BYTES = 8 * 1024 * 1024;+-function runGit(args: readonly string[], cwd: string, timeoutMs = 30_000): Promise<TerminalCommandResult> {+function runGit(+ args: readonly string[],+ cwd: string,+ timeoutMs = 30_000,+ maxBufferBytes = DEFAULT_GIT_MAX_BUFFER_BYTES,+): Promise<TerminalCommandResult> {
return new Promise((resolve, reject) => {
const child = spawn("git", args, {
cwd,
env: process.env,
stdio: ["ignore", "pipe", "pipe"],
});
let stdout = "";
let stderr = "";
let timedOut = false;
+ let stdoutBytes = 0;+ let stderrBytes = 0;+ let settled = false;
const timeout = setTimeout(() => {
timedOut = true;
child.kill("SIGTERM");
setTimeout(() => {
if (!child.killed) child.kill("SIGKILL");
}, 1_000).unref();
}, timeoutMs);
+ const fail = (error: Error) => {+ if (settled) return;+ settled = true;+ clearTimeout(timeout);+ child.kill("SIGTERM");+ reject(error);+ };+
child.stdout?.on("data", (chunk: Buffer) => {
- stdout += chunk.toString();+ const text = chunk.toString();+ stdout += text;+ stdoutBytes += Buffer.byteLength(text);+ if (stdoutBytes > maxBufferBytes) {+ fail(+ new Error(+ `${commandLabel(args)} exceeded stdout buffer limit (${maxBufferBytes} bytes).`,+ ),+ );+ }
});
child.stderr?.on("data", (chunk: Buffer) => {
- stderr += chunk.toString();+ const text = chunk.toString();+ stderr += text;+ stderrBytes += Buffer.byteLength(text);+ if (stderrBytes > maxBufferBytes) {+ fail(+ new Error(+ `${commandLabel(args)} exceeded stderr buffer limit (${maxBufferBytes} bytes).`,+ ),+ );+ }
});
child.on("error", (error) => {
clearTimeout(timeout);
reject(error);
});
child.on("close", (code, signal) => {
+ if (settled) return;+ settled = true;
clearTimeout(timeout);
resolve({ stdout, stderr, code: code ?? null, signal: signal ?? null, timedOut });
});
});
}

As per coding guidelines: Maintain predictable behavior under load and during failures (session restarts, reconnects, partial streams).

Comment threadapps/web/src/components/ChatView.tsx Outdated
Co-authored-by: codex <codex@users.noreply.github.com>
await runGitOrThrow(cwd, args, { allowNonZeroExit });
}

async gitStdout(cwd: string, args: readonly string[], allowNonZeroExit = false): Promise<string> {

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.

🟡 Medium

src/git.ts:359gitStdout discards stderr even on success, so the truncation warning from runGit is lost. Consider checking if output was truncated and either throwing an error or returning a flag so callers know the data is incomplete.

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/git.ts around line 359:
`gitStdout` discards `stderr` even on success, so the truncation warning from `runGit` is lost. Consider checking if output was truncated and either throwing an error or returning a flag so callers know the data is incomplete.

},
);

const rawOutput = (await fs.readFile(outputPath, "utf8")).trim();

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.

🟠 High

src/codexTextGenerator.ts:145fs.readFile at line 145 has no size limit, unlike the maxBufferBytes guard on stdout/stderr in runProcess. Consider adding a file size check (via fs.stat) before reading to prevent OOM if codex produces unexpectedly large output.

- const rawOutput = (await fs.readFile(outputPath, "utf8")).trim();+ const MAX_OUTPUT_BYTES = 8 * 1024 * 1024;+ const stat = await fs.stat(outputPath);+ if (stat.size > MAX_OUTPUT_BYTES) {+ throw new Error(`Codex output exceeded size limit (${MAX_OUTPUT_BYTES} bytes).`);+ }+ const rawOutput = (await fs.readFile(outputPath, "utf8")).trim();

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/codexTextGenerator.ts around line 145:
`fs.readFile` at line 145 has no size limit, unlike the `maxBufferBytes` guard on stdout/stderr in `runProcess`. Consider adding a file size check (via `fs.stat`) before reading to prevent OOM if `codex` produces unexpectedly large output.

- include open PR metadata in git status with graceful `gh` failure handling
- centralize git command execution in `GitCoreService` via `runProcess` with truncation support
- add PTY spawn-helper permission fixes, shell fallback retries, and tests for new behavior
}

async createWorktree(input: GitCreateWorktreeInput): Promise<GitCreateWorktreeResult> {
const sanitizedBranch = input.newBranch.replace(/\//g, "-");

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.

🟡 Medium

src/git.ts:382 Default worktreePath can collide across repos and branches (sanitized names and shared basenames). Suggest deriving a unique, deterministic path (e.g., hash of full repo path + branch) or document if intentional.

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/git.ts around line 382:
Default `worktreePath` can collide across repos and branches (sanitized names and shared basenames). Suggest deriving a unique, deterministic path (e.g., hash of full repo path + branch) or document if intentional.

args.push("-m", trimmedBody);
}
await this.git(cwd, args);
const commitSha = trimStdout(await this.gitStdout(cwd, ["rev-parse", "HEAD"]));

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.

🟢 Low

src/git.ts:256 Race condition: another commit between git commit and git rev-parse HEAD could return the wrong SHA. Consider using git rev-parse HEAD output from the commit command itself, or use git commit --porcelain to get the SHA atomically.

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/git.ts around line 256:
Race condition: another commit between `git commit` and `git rev-parse HEAD` could return the wrong SHA. Consider using `git rev-parse HEAD` output from the commit command itself, or use `git commit --porcelain` to get the SHA atomically.

const worktreeMap = new Map<string, string>();
if (worktreeList.code === 0) {
let currentPath: string | null = null;
for (const line of worktreeList.stdout.split("\n")) {

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.

🟡 Medium

src/git.ts:347 On Windows, split("\n") leaves trailing \r in paths, causing fs.existsSync to fail. Consider using split(/\r?\n/) instead.

Suggested change
for(constlineofworktreeList.stdout.split("\n")){
for(constlineofworktreeList.stdout.split(/\r?\n/)){

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/git.ts around line 347:
On Windows, `split("\n")` leaves trailing `\r` in paths, causing `fs.existsSync` to fail. Consider using `split(/\r?\n/)` instead.

}

async removeWorktree(input: GitRemoveWorktreeInput): Promise<void> {
await executeGit(input.cwd, ["worktree", "remove", input.path], {

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.

🟢 Low

src/git.ts:402 Consider adding -- before input.path to prevent paths starting with - from being interpreted as git options.

Suggested change
awaitexecuteGit(input.cwd,["worktree","remove",input.path],{
awaitexecuteGit(input.cwd,["worktree","remove","--",input.path],{

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/git.ts around line 402:
Consider adding `--` before `input.path` to prevent paths starting with `-` from being interpreted as git options.

- Replace custom inline SVGs with Lucide commit, push, and GitHub icons
- Keep git action behavior unchanged while applying minor style cleanup
Comment threadapps/web/src/components/ChatView.tsx Outdated
juliusmarmingeand others added 3 commits February 12, 2026 12:22
- Add a Git action confirmation modal with live commit/push/PR progress states
- Accept optional `commitMessage` input and skip AI message generation when provided
- Expand server and contracts tests for custom commit message handling
Co-authored-by: codex <codex@users.noreply.github.com>
- Move git menu, modal, and stacked action logic out of `ChatView`
- Render a new `GitActionsControl` wired with `api` and `gitCwd`
- Split Git actions into context-aware Commit, Push, and PR menu items
- Add modal action selection with clearer availability and disabled-state guidance
- Migrate git status and immediate actions to React Query and add a custom GitHub icon
Co-authored-by: codex <codex@users.noreply.github.com>
@coderabbitaicoderabbitaiBot mentioned this pull request Feb 15, 2026
jjalangtry pushed a commit to jjalangtry/t3code that referenced this pull request Mar 16, 2026
dcherrera pushed a commit to dcherrera/t3code that referenced this pull request Jul 7, 2026
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 16, 2026
Makes declarative settings real: a plugin declares a schema and the HOST renders the
form. The plugin ships no form, no RPC, no storage.
The loader now synthesises a settings page from `definition.settings`, so the
declaration is no longer inert (the previous commit shipped the declaration with
nothing consuming it). Generated before register(), so a plugin declaring both gets
its generated page first, then its own; the page id is fixed so a plugin's own
registerSettingsPage cannot shadow it.
PluginSettingsPage wires the concurrency story end to end: the draft carries a
revision, every save sends it back as expectedRevision, and a stale save is rejected
by the server rather than silently clobbering another tab. The server's message is
surfaced verbatim because "does not match the schema" and "changed elsewhere" need
different actions from the user.
The form opens on incompatible stored data and says so, rather than refusing — that
is exactly when the user needs it to repair the values, and the stored data is
preserved until a valid save (Sol spec review MUST pingdotgg#13's recovery half).
This also makes the previously-vacuous test assertable. It now asserts the generated
page appears, and is verified deletion-sensitive: removing the synthesis fails both
new tests. `declared: false` (no schema, or plugin disabled) renders an explanation
rather than an empty form.
Gates: typecheck 0 errors; web 1319 passed; server 1672 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 16, 2026
Sol spec review MUST pingdotgg#13 (detection half). The store recorded a schema fingerprint
from the first commit but nothing ever read it back, so an upgrade that changed a
plugin's schema under already-stored values went undetected — the values were read
as if they still matched, which is exactly the silent misread the fingerprint exists
to prevent.
readDraft now surfaces the fingerprint that produced the stored values, and the
settings RPC compares it against the plugin's CURRENT schema. A mismatch marks the
draft incompatible, which the settings page already renders as "these settings need
attention" with the form open for repair.
The stored data is preserved on mismatch, not discarded: the user may need to read
the old values to reconstruct the new ones, and a plugin that briefly downgrades
should not lose its config. Recovery is a valid save, which rewrites both the values
and the fingerprint.
Tests verified deletion-sensitive: returning a null fingerprint from readDraft fails
both fingerprint tests.
Remaining for MUST pingdotgg#13: hostApi.settings.get should fail InvalidStored on drift
rather than relying on decode alone. The decode already fails for an incompatible
shape in the common case; the fingerprint makes it explicit. Tracked, not claimed.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 16, 2026
Sol spec review MUST pingdotgg#13, plugin-read half. hostApi.settings.get now rejects stored
values whose fingerprint does not match the plugin's current schema, instead of
relying on decode to notice.
Decode is not sufficient and that is the whole point: a widening change (adding an
optional field, relaxing a filter) still decodes cleanly, so the plugin would
silently run on values written for a shape it no longer declares. Comparing the
fingerprint makes the mismatch the fact being checked rather than a side effect of
decoding. The error carries no values — only the plugin id.
KNOWN GAP, stated rather than implied: this path has no test. I wrote one with a
fixture that probes settings.get from a service and records the Exit tag to its data
dir; the probe did not get scheduled within the test's wait, and the "<probe never
ran>" guard I built into it caught that rather than letting it pass vacuously against
whatever I asserted. Rather than ship a test that cannot reliably observe the
behaviour — or one that asserts only "the plugin activated", which would pass with
this check deleted — the tests are removed and the gap recorded.
Store-level fingerprint behaviour IS covered and verified deletion-sensitive
(PluginSettingsStore.test.ts); what is untested is specifically the capability read
path rejecting on drift. Needs a fixture harness that can await a forked service.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 16, 2026
Makes declarative settings real: a plugin declares a schema and the HOST renders the
form. The plugin ships no form, no RPC, no storage.
The loader now synthesises a settings page from `definition.settings`, so the
declaration is no longer inert (the previous commit shipped the declaration with
nothing consuming it). Generated before register(), so a plugin declaring both gets
its generated page first, then its own; the page id is fixed so a plugin's own
registerSettingsPage cannot shadow it.
PluginSettingsPage wires the concurrency story end to end: the draft carries a
revision, every save sends it back as expectedRevision, and a stale save is rejected
by the server rather than silently clobbering another tab. The server's message is
surfaced verbatim because "does not match the schema" and "changed elsewhere" need
different actions from the user.
The form opens on incompatible stored data and says so, rather than refusing — that
is exactly when the user needs it to repair the values, and the stored data is
preserved until a valid save (Sol spec review MUST pingdotgg#13's recovery half).
This also makes the previously-vacuous test assertable. It now asserts the generated
page appears, and is verified deletion-sensitive: removing the synthesis fails both
new tests. `declared: false` (no schema, or plugin disabled) renders an explanation
rather than an empty form.
Gates: typecheck 0 errors; web 1319 passed; server 1672 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 16, 2026
Sol spec review MUST pingdotgg#13 (detection half). The store recorded a schema fingerprint
from the first commit but nothing ever read it back, so an upgrade that changed a
plugin's schema under already-stored values went undetected — the values were read
as if they still matched, which is exactly the silent misread the fingerprint exists
to prevent.
readDraft now surfaces the fingerprint that produced the stored values, and the
settings RPC compares it against the plugin's CURRENT schema. A mismatch marks the
draft incompatible, which the settings page already renders as "these settings need
attention" with the form open for repair.
The stored data is preserved on mismatch, not discarded: the user may need to read
the old values to reconstruct the new ones, and a plugin that briefly downgrades
should not lose its config. Recovery is a valid save, which rewrites both the values
and the fingerprint.
Tests verified deletion-sensitive: returning a null fingerprint from readDraft fails
both fingerprint tests.
Remaining for MUST pingdotgg#13: hostApi.settings.get should fail InvalidStored on drift
rather than relying on decode alone. The decode already fails for an incompatible
shape in the common case; the fingerprint makes it explicit. Tracked, not claimed.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 16, 2026
Sol spec review MUST pingdotgg#13, plugin-read half. hostApi.settings.get now rejects stored
values whose fingerprint does not match the plugin's current schema, instead of
relying on decode to notice.
Decode is not sufficient and that is the whole point: a widening change (adding an
optional field, relaxing a filter) still decodes cleanly, so the plugin would
silently run on values written for a shape it no longer declares. Comparing the
fingerprint makes the mismatch the fact being checked rather than a side effect of
decoding. The error carries no values — only the plugin id.
KNOWN GAP, stated rather than implied: this path has no test. I wrote one with a
fixture that probes settings.get from a service and records the Exit tag to its data
dir; the probe did not get scheduled within the test's wait, and the "<probe never
ran>" guard I built into it caught that rather than letting it pass vacuously against
whatever I asserted. Rather than ship a test that cannot reliably observe the
behaviour — or one that asserts only "the plugin activated", which would pass with
this check deleted — the tests are removed and the gap recorded.
Store-level fingerprint behaviour IS covered and verified deletion-sensitive
(PluginSettingsStore.test.ts); what is untested is specifically the capability read
path rejecting on drift. Needs a fixture harness that can await a forked service.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 17, 2026
Makes declarative settings real: a plugin declares a schema and the HOST renders the
form. The plugin ships no form, no RPC, no storage.
The loader now synthesises a settings page from `definition.settings`, so the
declaration is no longer inert (the previous commit shipped the declaration with
nothing consuming it). Generated before register(), so a plugin declaring both gets
its generated page first, then its own; the page id is fixed so a plugin's own
registerSettingsPage cannot shadow it.
PluginSettingsPage wires the concurrency story end to end: the draft carries a
revision, every save sends it back as expectedRevision, and a stale save is rejected
by the server rather than silently clobbering another tab. The server's message is
surfaced verbatim because "does not match the schema" and "changed elsewhere" need
different actions from the user.
The form opens on incompatible stored data and says so, rather than refusing — that
is exactly when the user needs it to repair the values, and the stored data is
preserved until a valid save (Sol spec review MUST pingdotgg#13's recovery half).
This also makes the previously-vacuous test assertable. It now asserts the generated
page appears, and is verified deletion-sensitive: removing the synthesis fails both
new tests. `declared: false` (no schema, or plugin disabled) renders an explanation
rather than an empty form.
Gates: typecheck 0 errors; web 1319 passed; server 1672 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 17, 2026
Sol spec review MUST pingdotgg#13 (detection half). The store recorded a schema fingerprint
from the first commit but nothing ever read it back, so an upgrade that changed a
plugin's schema under already-stored values went undetected — the values were read
as if they still matched, which is exactly the silent misread the fingerprint exists
to prevent.
readDraft now surfaces the fingerprint that produced the stored values, and the
settings RPC compares it against the plugin's CURRENT schema. A mismatch marks the
draft incompatible, which the settings page already renders as "these settings need
attention" with the form open for repair.
The stored data is preserved on mismatch, not discarded: the user may need to read
the old values to reconstruct the new ones, and a plugin that briefly downgrades
should not lose its config. Recovery is a valid save, which rewrites both the values
and the fingerprint.
Tests verified deletion-sensitive: returning a null fingerprint from readDraft fails
both fingerprint tests.
Remaining for MUST pingdotgg#13: hostApi.settings.get should fail InvalidStored on drift
rather than relying on decode alone. The decode already fails for an incompatible
shape in the common case; the fingerprint makes it explicit. Tracked, not claimed.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 17, 2026
Sol spec review MUST pingdotgg#13, plugin-read half. hostApi.settings.get now rejects stored
values whose fingerprint does not match the plugin's current schema, instead of
relying on decode to notice.
Decode is not sufficient and that is the whole point: a widening change (adding an
optional field, relaxing a filter) still decodes cleanly, so the plugin would
silently run on values written for a shape it no longer declares. Comparing the
fingerprint makes the mismatch the fact being checked rather than a side effect of
decoding. The error carries no values — only the plugin id.
KNOWN GAP, stated rather than implied: this path has no test. I wrote one with a
fixture that probes settings.get from a service and records the Exit tag to its data
dir; the probe did not get scheduled within the test's wait, and the "<probe never
ran>" guard I built into it caught that rather than letting it pass vacuously against
whatever I asserted. Rather than ship a test that cannot reliably observe the
behaviour — or one that asserts only "the plugin activated", which would pass with
this check deleted — the tests are removed and the gap recorded.
Store-level fingerprint behaviour IS covered and verified deletion-sensitive
(PluginSettingsStore.test.ts); what is untested is specifically the capability read
path rejecting on drift. Needs a fixture harness that can await a forked service.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 30, 2026
Makes declarative settings real: a plugin declares a schema and the HOST renders the
form. The plugin ships no form, no RPC, no storage.
The loader now synthesises a settings page from `definition.settings`, so the
declaration is no longer inert (the previous commit shipped the declaration with
nothing consuming it). Generated before register(), so a plugin declaring both gets
its generated page first, then its own; the page id is fixed so a plugin's own
registerSettingsPage cannot shadow it.
PluginSettingsPage wires the concurrency story end to end: the draft carries a
revision, every save sends it back as expectedRevision, and a stale save is rejected
by the server rather than silently clobbering another tab. The server's message is
surfaced verbatim because "does not match the schema" and "changed elsewhere" need
different actions from the user.
The form opens on incompatible stored data and says so, rather than refusing — that
is exactly when the user needs it to repair the values, and the stored data is
preserved until a valid save (Sol spec review MUST pingdotgg#13's recovery half).
This also makes the previously-vacuous test assertable. It now asserts the generated
page appears, and is verified deletion-sensitive: removing the synthesis fails both
new tests. `declared: false` (no schema, or plugin disabled) renders an explanation
rather than an empty form.
Gates: typecheck 0 errors; web 1319 passed; server 1672 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 30, 2026
Sol spec review MUST pingdotgg#13 (detection half). The store recorded a schema fingerprint
from the first commit but nothing ever read it back, so an upgrade that changed a
plugin's schema under already-stored values went undetected — the values were read
as if they still matched, which is exactly the silent misread the fingerprint exists
to prevent.
readDraft now surfaces the fingerprint that produced the stored values, and the
settings RPC compares it against the plugin's CURRENT schema. A mismatch marks the
draft incompatible, which the settings page already renders as "these settings need
attention" with the form open for repair.
The stored data is preserved on mismatch, not discarded: the user may need to read
the old values to reconstruct the new ones, and a plugin that briefly downgrades
should not lose its config. Recovery is a valid save, which rewrites both the values
and the fingerprint.
Tests verified deletion-sensitive: returning a null fingerprint from readDraft fails
both fingerprint tests.
Remaining for MUST pingdotgg#13: hostApi.settings.get should fail InvalidStored on drift
rather than relying on decode alone. The decode already fails for an incompatible
shape in the common case; the fingerprint makes it explicit. Tracked, not claimed.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 30, 2026
Sol spec review MUST pingdotgg#13, plugin-read half. hostApi.settings.get now rejects stored
values whose fingerprint does not match the plugin's current schema, instead of
relying on decode to notice.
Decode is not sufficient and that is the whole point: a widening change (adding an
optional field, relaxing a filter) still decodes cleanly, so the plugin would
silently run on values written for a shape it no longer declares. Comparing the
fingerprint makes the mismatch the fact being checked rather than a side effect of
decoding. The error carries no values — only the plugin id.
KNOWN GAP, stated rather than implied: this path has no test. I wrote one with a
fixture that probes settings.get from a service and records the Exit tag to its data
dir; the probe did not get scheduled within the test's wait, and the "<probe never
ran>" guard I built into it caught that rather than letting it pass vacuously against
whatever I asserted. Rather than ship a test that cannot reliably observe the
behaviour — or one that asserts only "the plugin activated", which would pass with
this check deleted — the tests are removed and the gap recorded.
Store-level fingerprint behaviour IS covered and verified deletion-sensitive
(PluginSettingsStore.test.ts); what is untested is specifically the capability read
path rejecting on drift. Needs a fixture harness that can await a forked service.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 30, 2026
Makes declarative settings real: a plugin declares a schema and the HOST renders the
form. The plugin ships no form, no RPC, no storage.
The loader now synthesises a settings page from `definition.settings`, so the
declaration is no longer inert (the previous commit shipped the declaration with
nothing consuming it). Generated before register(), so a plugin declaring both gets
its generated page first, then its own; the page id is fixed so a plugin's own
registerSettingsPage cannot shadow it.
PluginSettingsPage wires the concurrency story end to end: the draft carries a
revision, every save sends it back as expectedRevision, and a stale save is rejected
by the server rather than silently clobbering another tab. The server's message is
surfaced verbatim because "does not match the schema" and "changed elsewhere" need
different actions from the user.
The form opens on incompatible stored data and says so, rather than refusing — that
is exactly when the user needs it to repair the values, and the stored data is
preserved until a valid save (Sol spec review MUST pingdotgg#13's recovery half).
This also makes the previously-vacuous test assertable. It now asserts the generated
page appears, and is verified deletion-sensitive: removing the synthesis fails both
new tests. `declared: false` (no schema, or plugin disabled) renders an explanation
rather than an empty form.
Gates: typecheck 0 errors; web 1319 passed; server 1672 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 30, 2026
Sol spec review MUST pingdotgg#13 (detection half). The store recorded a schema fingerprint
from the first commit but nothing ever read it back, so an upgrade that changed a
plugin's schema under already-stored values went undetected — the values were read
as if they still matched, which is exactly the silent misread the fingerprint exists
to prevent.
readDraft now surfaces the fingerprint that produced the stored values, and the
settings RPC compares it against the plugin's CURRENT schema. A mismatch marks the
draft incompatible, which the settings page already renders as "these settings need
attention" with the form open for repair.
The stored data is preserved on mismatch, not discarded: the user may need to read
the old values to reconstruct the new ones, and a plugin that briefly downgrades
should not lose its config. Recovery is a valid save, which rewrites both the values
and the fingerprint.
Tests verified deletion-sensitive: returning a null fingerprint from readDraft fails
both fingerprint tests.
Remaining for MUST pingdotgg#13: hostApi.settings.get should fail InvalidStored on drift
rather than relying on decode alone. The decode already fails for an incompatible
shape in the common case; the fingerprint makes it explicit. Tracked, not claimed.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 30, 2026
Sol spec review MUST pingdotgg#13, plugin-read half. hostApi.settings.get now rejects stored
values whose fingerprint does not match the plugin's current schema, instead of
relying on decode to notice.
Decode is not sufficient and that is the whole point: a widening change (adding an
optional field, relaxing a filter) still decodes cleanly, so the plugin would
silently run on values written for a shape it no longer declares. Comparing the
fingerprint makes the mismatch the fact being checked rather than a side effect of
decoding. The error carries no values — only the plugin id.
KNOWN GAP, stated rather than implied: this path has no test. I wrote one with a
fixture that probes settings.get from a service and records the Exit tag to its data
dir; the probe did not get scheduled within the test's wait, and the "<probe never
ran>" guard I built into it caught that rather than letting it pass vacuously against
whatever I asserted. Rather than ship a test that cannot reliably observe the
behaviour — or one that asserts only "the plugin activated", which would pass with
this check deleted — the tests are removed and the gap recorded.
Store-level fingerprint behaviour IS covered and verified deletion-sensitive
(PluginSettingsStore.test.ts); what is untested is specifically the capability read
path rejecting on drift. Needs a fixture harness that can await a forked service.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 31, 2026
Makes declarative settings real: a plugin declares a schema and the HOST renders the
form. The plugin ships no form, no RPC, no storage.
The loader now synthesises a settings page from `definition.settings`, so the
declaration is no longer inert (the previous commit shipped the declaration with
nothing consuming it). Generated before register(), so a plugin declaring both gets
its generated page first, then its own; the page id is fixed so a plugin's own
registerSettingsPage cannot shadow it.
PluginSettingsPage wires the concurrency story end to end: the draft carries a
revision, every save sends it back as expectedRevision, and a stale save is rejected
by the server rather than silently clobbering another tab. The server's message is
surfaced verbatim because "does not match the schema" and "changed elsewhere" need
different actions from the user.
The form opens on incompatible stored data and says so, rather than refusing — that
is exactly when the user needs it to repair the values, and the stored data is
preserved until a valid save (Sol spec review MUST pingdotgg#13's recovery half).
This also makes the previously-vacuous test assertable. It now asserts the generated
page appears, and is verified deletion-sensitive: removing the synthesis fails both
new tests. `declared: false` (no schema, or plugin disabled) renders an explanation
rather than an empty form.
Gates: typecheck 0 errors; web 1319 passed; server 1672 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 31, 2026
Sol spec review MUST pingdotgg#13 (detection half). The store recorded a schema fingerprint
from the first commit but nothing ever read it back, so an upgrade that changed a
plugin's schema under already-stored values went undetected — the values were read
as if they still matched, which is exactly the silent misread the fingerprint exists
to prevent.
readDraft now surfaces the fingerprint that produced the stored values, and the
settings RPC compares it against the plugin's CURRENT schema. A mismatch marks the
draft incompatible, which the settings page already renders as "these settings need
attention" with the form open for repair.
The stored data is preserved on mismatch, not discarded: the user may need to read
the old values to reconstruct the new ones, and a plugin that briefly downgrades
should not lose its config. Recovery is a valid save, which rewrites both the values
and the fingerprint.
Tests verified deletion-sensitive: returning a null fingerprint from readDraft fails
both fingerprint tests.
Remaining for MUST pingdotgg#13: hostApi.settings.get should fail InvalidStored on drift
rather than relying on decode alone. The decode already fails for an incompatible
shape in the common case; the fingerprint makes it explicit. Tracked, not claimed.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 31, 2026
Sol spec review MUST pingdotgg#13, plugin-read half. hostApi.settings.get now rejects stored
values whose fingerprint does not match the plugin's current schema, instead of
relying on decode to notice.
Decode is not sufficient and that is the whole point: a widening change (adding an
optional field, relaxing a filter) still decodes cleanly, so the plugin would
silently run on values written for a shape it no longer declares. Comparing the
fingerprint makes the mismatch the fact being checked rather than a side effect of
decoding. The error carries no values — only the plugin id.
KNOWN GAP, stated rather than implied: this path has no test. I wrote one with a
fixture that probes settings.get from a service and records the Exit tag to its data
dir; the probe did not get scheduled within the test's wait, and the "<probe never
ran>" guard I built into it caught that rather than letting it pass vacuously against
whatever I asserted. Rather than ship a test that cannot reliably observe the
behaviour — or one that asserts only "the plugin activated", which would pass with
this check deleted — the tests are removed and the gap recorded.
Store-level fingerprint behaviour IS covered and verified deletion-sensitive
(PluginSettingsStore.test.ts); what is untested is specifically the capability read
path rejecting on drift. Needs a fixture harness that can await a forked service.
Gates: typecheck 0 errors; server 1675 passed.
aorwall added a commit to aorwall/t3code that referenced this pull request Aug 2, 2026
* docs: spec for preview servers in the T3 Code web app
Approved spec covering the Servers right-panel view, Moatless-owned preview
tabs backed by Redis, and an iframe renderer that lets the existing browser
panel work outside Electron.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat: add the servers.* contract group
Three reads over the servers a thread's environment declares: list them, watch
their status, and follow one's log. Field names are taken verbatim from the
host's own server record so the server side is a rename-free serialization.
Nothing here starts, stops or reconfigures a server; those methods are
specified elsewhere and deliberately out of scope.
The preview module docstring said the preview is desktop-only and that the
desktop renderer mediates. Both stop being true in the same change, so the
docstring now describes the surface by capability rather than by client.
* feat: carry the servers.* group through the client runtime and reference server
Client-side atoms for the three reads, with the log subscription folding
lines into a bounded buffer so a remounted panel keeps what it received
rather than starting over.
The reference server declares no thread servers - it runs threads on the
machine it is on - so it answers the list empty and both subscriptions
silent. A hosted environment answers the same three methods with real data.
Also splits the preview runtime capability in two. One boolean was answering
both "can this runtime show a page" and "can this runtime read the page it
shows", which are the same question only on the desktop app.
* feat: host preview pages in a sandboxed frame on the web
The web app could not show a task's page at all. It can now: one frame per
open tab, mounted at the app root so it survives the panel collapsing, and
positioned by the same surface rect the desktop webview uses.
The frame is driven and never read. Navigation writes to the server and the
frame follows; refresh replaces the element because a cross-origin frame has
no reload and reassigning src would grow the parent's history; back and
forward are absent rather than dead.
Two things a frame cannot report, the panel says from elsewhere. A page that
is not there is explained by what its server says about itself, which is
better than a net error - "installing" is an answer no net error carries. A
page that renders nothing while its server says it is running gets a hint
offered as a hint, because a frame-ancestors refusal and the preview host's
own 401 look identical from outside and neither fires an event.
* feat: add a Servers view to the right panel
One row per server the thread's environment declares, with its status kept
current by the subscription, its log on demand, and an Open that hands the
URL to a browser tab.
Everything in it is a read - a row that says failed offers its log and no
button. Restarting a server is a write and lands elsewhere.
The right panel's persisted state moves to version 8 for the new surface
kind, and its migration now drops surfaces whose kind this build does not
know. That is what makes the version bump safe to downgrade away from: the
rest of the workspace survives and only the unknown tab is lost.
* test: cover the browser preview surface and the servers view
Two products share these schemas and no test process, so the seam is a set
of real Moatless responses checked in here and decoded by the schemas
themselves. When its projection changes the fixture changes with it in one
commit, and the decode test is what fails if the two drift.
The rest covers what the change actually promises: the capability answers
three runtimes, the chrome row omits controls rather than disabling them,
the frame re-keys instead of reassigning src, exactly one browser host
renders, and the panel state survives the version bump while dropping a kind
this build does not know.
* fix: state the environment's absence rather than implying it from a row
The never-provisioned fixture claimed a server is listed as `stopped` with
no URL. It is not. Moatless resolves status config-first from a NotFound
pod, which falls back to `starting` with the ingress URL the port will have
— so the panel showed `starting` forever for an environment that does not
exist, and offered an Open button pointing at a 502.
The fixture now carries what the backend produces, and the panel states the
environment's own status above the list instead of leaving it to be inferred
from rows that cannot say it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: track the fork's own delta, not just what upstream does to it
The policy could answer "who wins this conflict" but not "what did we change",
and the second is the question you are actually asking when git stops on a line
you do not recognise. The path table only ever listed files we expected to fight
over — a much smaller set than the files we changed. The whole thread-servers
group, around forty files, appeared nowhere.
§3 is that missing list, and a fifth hazard in the preamble names the failure it
prevents: a conflict resolved toward upstream because nobody could tell our line
was deliberate. Two consequences: the checklist asks for a row in the same commit
as the change rather than at the next merge, and reading the inventory is now a
step in resolving a conflict rather than something to remember.
Also adds path-policy rows for the highest-risk of those files —
`apps/server/src/ws.ts` and `RpcAuthorization.ts`, the only upstream server files
the fork touches, where the resolution is to take theirs and re-add three
`servers.*` entries.
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
aorwall pushed a commit to aorwall/t3code that referenced this pull request Aug 12, 2026
The 2026-08-08 upstream merge took 89 minutes. Most of that was not conflict
resolution; it was performing checks that existed only as prose in
docs/fork/upstream-merge-inventory.md, one at a time, and rediscovering two
things the doc could have told me before I started.
Five changes, in the order they pay off.
**The inventory is data now.** docs/fork/inventory.json holds the fork-owned
concerns, path policy, fork inventory, tripwires, deliberately deleted upstream
paths, off-repository state and convergence entries. The markdown keeps only
what a check cannot hold — the four re-application deltas, where the
unsupported-method set comes from, and the reasoning behind the path policy
rule — and drops from 784 lines to 243.
**The checks run.** Five dependency-free scripts under the skill:
- `preflight.mjs` — the range, stale entries, the owned-concern sweep, and the
conflict forecast: every file both sides touched, grouped by the verdict that
resolves it, before the merge starts.
- `inventory-check.mjs` — every inventory path read back out of upstream/main.
This is the one that matters: last merge, upstream moved SidebarV2's content
into Sidebar.tsx and Sidebar.tsx's into LegacySidebar.tsx. Git cannot see a
content swap as a rename — it is a delete paired with a modify, which no -M
threshold detects — so it surfaced as a modify/delete conflict mid-merge.
Run against the pre-merge tree, this check names it in about a second.
- `tripwires.mjs`, `unsupported-methods.mjs`, `verify.mjs`.
Running inventory-check against the current tree immediately found three dead
path-policy entries that had been faithfully transcribed forward through
several merges, for directories deleted in pingdotgg#13 and pingdotgg#26.
**Verification is one command, earlier.** `verify.mjs` runs tripwires, the
unsupported-method derivation, format, lint, types and tests — and does not
stop at the first failure, so a formatting nit no longer hides the type errors
behind it. It raises the heap the web suite needs, whose failure mode is
otherwise an exit 137 that reads like a real test failure. The skill now runs
it *before* the documentation steps, because its output is their input.
**Counts come out of prose.** Both snapshots the last merge relied on were
stale. "38 of 87 methods" and "Clerk 4 / pairing 73 / session bootstrap 9" are
replaced by the command that derives them.
**The fork test no longer hard-codes upstream paths.** features.test.ts read
`../components/Sidebar.tsx?raw`; when upstream renamed that file the whole
suite failed to build, at a module path, saying nothing about the gate. It now
reads the guarded files out of inventory.json and reaches them through
import.meta.glob, so a rename fails as a named assertion that says which entry
to re-point — and the test and the merge scripts read the same guards, so they
cannot drift apart.
Verified: fmt, lint, typecheck and 1908 tests pass. `tripwires.mjs` reports one
finding, which is genuine and already tracked — thread-transfer-report.yml went
active on GitHub when the merge branch was pushed and still needs disabling.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
NeilTheFisher pushed a commit to NeilTheFisher/t3code that referenced this pull request Aug 18, 2026
AFLabAI added a commit to AFLabAI/t3code that referenced this pull request Sep 2, 2026
Increase diagnostic visibility by appending complete vp check stdout/stderr to job summary instead of truncating. This exposes all 11 lint errors detected in RUN pingdotgg#13.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Sign up for freeto 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

@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat: Commit, Push and Create PR actions - #13

Merged
juliusmarminge merged 18 commits into
mainfrom
codex/add-github-commit-push-ui
Feb 12, 2026
Merged

feat: Commit, Push and Create PR actions#13
juliusmarminge merged 18 commits into
mainfrom
codex/add-github-commit-push-ui

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Feb 12, 2026

Copy link
Copy Markdown
Member

Open with Devin

Summary by CodeRabbit

  • New Features

    • AI-powered generation for commit messages and PR title/body.
    • New Git core & manager with stacked workflows (commit, commit & push, commit & push & create/open PR), richer status details, and WebSocket/native endpoints to run Git status and actions.
    • UI: Git actions menu with real-time status, action execution, notices and error handling.
  • Chores

    • Robust process runner with buffer and timeout safeguards.
  • Tests

    • Extensive end-to-end and unit tests covering Git flows, PR lifecycle, manager behavior, and contract schemas.

@coderabbitai

coderabbitaiBot commented Feb 12, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds a Git subsystem: process runner with buffer/timeouts, Git core service, GitManager orchestration, Codex-based commit/PR generation, WS API + server wiring, client UI integration, contracts/schemas for stacked git actions, and extensive tests.

Changes

Cohort / File(s)Summary
Text Generation Service
apps/server/src/coreServices.ts, apps/server/src/codexTextGenerator.ts
Defines TextGenerationService types and implements CodexTextGenerator with JSON schemas, validation/parsers, temp-file helpers, runCodexJson orchestration, and methods to generate commit messages and PR content.
Process Execution
apps/server/src/processRunner.ts
Adds runProcess with configurable maxBufferBytes, stdout/stderr accumulation and byte limits, timeout handling (SIGTERM→SIGKILL), spawn/exit normalization, and detailed error types.
Git Core Service
apps/server/src/git.ts
Introduces GitCoreService and helpers: enriched status/statusDetails, prepareCommitContext, commit, pushCurrentBranch, readRangeContext, readConfigValue, runGit helpers, and upstream/default-branch logic.
GitManager Orchestration
apps/server/src/gitManager.ts
Adds GitManager coordinating gitCore, processRunner, Codex text generation, and gh interactions for stacked actions (commit, push, PR), PR discovery/creation, temp-file PR bodies, and error normalization.
Server WS Integration & Tests
apps/server/src/wsServer.ts, apps/server/src/wsServer.test.ts
Wires GitManager into server options, exposes WS methods git.status and git.runStackedAction, and updates tests to inject/validate gitManager delegation and error propagation.
Tests: GitManager & GitCore
apps/server/src/gitManager.test.ts, apps/server/src/git.test.ts
Adds end-to-end tests covering stacked actions, GH CLI scenarios, upstream behavior, detached HEAD, auth errors, and GitCoreService behaviors.
Client Integration
apps/web/src/wsNativeApi.ts, apps/web/src/components/ChatView.tsx
Extends native API with git.status and git.runStackedAction; integrates Git actions menu and UI state into ChatView with status polling, action execution, and notices.
Contracts / IPC / WS Methods & Tests
packages/contracts/src/git.ts, packages/contracts/src/git.test.ts, packages/contracts/src/ipc.ts, packages/contracts/src/ws.ts
Adds Zod schemas/types for git status and stacked actions, updates NativeApi.git signatures, expands WS_METHODS with git.status and git.runStackedAction, and adds schema tests.

Sequence Diagram(s)

sequenceDiagram
participant Client as Client (ChatView)
participant WS as WebSocket Server
participant GM as GitManager
participant GC as GitCoreService
participant Proc as ProcessRunner
participant Codex as Codex Service
participant GH as GitHub CLI
Client->>WS: git.runStackedAction(action, cwd)
WS->>GM: runStackedAction()
rect rgba(100,150,200,0.5)
Note over GM: Commit Step
GM->>GC: prepareCommitContext(cwd)
GC-->>GM: stagedSummary, stagedPatch
GM->>Codex: generateCommitMessage(diff)
Codex-->>GM: subject, body
GM->>Proc: git commit -m "..."
Proc-->>GM: commit result
end
rect rgba(100,200,150,0.5)
Note over GM: Push Step (if requested)
GM->>GC: pushCurrentBranch(cwd, upstream?)
GC-->>GM: push result
end
rect rgba(200,150,100,0.5)
Note over GM: PR Step (if requested)
GM->>GC: readRangeContext(base, head)
GC-->>GM: commitSummary, diffSummary, diffPatch
GM->>Codex: generatePrContent(rangeContext)
Codex-->>GM: title, body
GM->>GH: gh pr list --head branch
GH-->>GM: existing PRs
alt PR exists
GM->>GH: gh pr view PR_NUMBER
GH-->>GM: PR details
else
GM->>GH: gh pr create --title "..." --body file://tmp
GH-->>GM: new PR info
end
end
GM-->>WS: GitRunStackedActionResult
WS-->>Client: result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 2.56% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title 'feat: Commit, Push and Create PR actions' accurately captures the main feature added: three new Git workflow actions integrated into the UI and backend services.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch codex/add-github-commit-push-ui

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps

greptile-appsBot commented Feb 12, 2026

Copy link
Copy Markdown

Greptile Overview

Greptile Summary

This PR implements comprehensive Git stacked actions (commit, push, create PR) with AI-powered text generation for commit messages and PR content. The implementation spans server-side orchestration, robust process execution, WebSocket/IPC integration, and a polished React UI with real-time progress tracking.

Key changes:

  • New GitManager orchestrates stacked workflows (commit → push → PR) with proper error handling and GitHub CLI integration
  • GitCoreService provides low-level Git operations using direct spawn (no shell), with buffer management and timeout safeguards
  • CodexTextGenerator generates commit messages and PR content via Codex CLI with structured JSON output schemas
  • ProcessRunner implements robust subprocess execution with truncation support, graceful termination, and buffer limit enforcement
  • GitActionsControl UI component provides modal-based workflow with step-by-step progress, custom commit messages, and error states
  • Comprehensive test coverage across unit tests (git.test.ts, processRunner.test.ts) and integration tests (gitManager.test.ts)
  • WebSocket and IPC layers extended to expose git.status and git.runStackedAction methods to both web and desktop clients

Architecture aligns with project priorities:

  • Performance: Direct spawn without shell overhead, buffer limits prevent memory issues
  • Reliability: Proper timeout handling, graceful degradation (PR lookup is best-effort), temp file cleanup
  • Predictable behavior: Structured status tracking, deterministic base branch resolution, comprehensive error normalization

Confidence Score: 5/5

  • This PR is safe to merge with high confidence
  • The implementation demonstrates strong engineering discipline: comprehensive test coverage (490 lines in gitManager.test.ts alone), proper error handling with normalized error messages, robust subprocess management with buffer limits and timeouts, dependency injection for testability, and alignment with project priorities (performance, reliability, predictable behavior). The only noted issue is duplicate commit message sanitization which is cosmetic and doesn't affect functionality.
  • No files require special attention

Important Files Changed

FilenameOverview
apps/server/src/git.tsAdds comprehensive Git operations with robust error handling, proper spawn arguments, and buffer management for status, commit, push, and range context operations
apps/server/src/gitManager.tsImplements high-level orchestration for stacked Git workflows (commit, push, PR) with AI text generation, proper dependency injection, and GitHub CLI integration
apps/server/src/codexTextGenerator.tsImplements AI-powered text generation for commit messages and PR content using Codex CLI with structured JSON output, proper sanitization, and temp file cleanup
apps/server/src/processRunner.tsAdds robust process execution with buffer limit enforcement, timeout handling, graceful termination (SIGTERM then SIGKILL), and truncation support
apps/server/src/wsServer.tsIntegrates GitManager into WebSocket server to expose git.status and git.runStackedAction methods via WS protocol
packages/contracts/src/git.tsExtends Git contracts with Zod schemas for stacked action API (status, commit, push, PR) with comprehensive validation rules
apps/web/src/components/GitActionsControl.tsxImplements comprehensive Git actions UI with modal workflow, real-time progress tracking, step-by-step execution, error handling, and PR link opening

Sequence Diagram

sequenceDiagram
participant User
participant GitActionsControl
participant NativeApi
participant GitManager
participant GitCore
participant CodexTextGenerator
participant GitCLI
participant GitHubCLI
User->>GitActionsControl: Click "Commit and create PR"
GitActionsControl->>GitActionsControl: Open modal, set action
User->>GitActionsControl: Confirm action
GitActionsControl->>NativeApi: git.runStackedAction(commit)
NativeApi->>GitManager: runStackedAction(commit)
GitManager->>GitCore: statusDetails(cwd)
GitCore->>GitCLI: git status --porcelain=2
GitCLI-->>GitCore: status output
GitCore-->>GitManager: branch, upstream info
GitManager->>GitCore: prepareCommitContext(cwd)
GitCore->>GitCLI: git add -A
GitCore->>GitCLI: git diff --cached
GitCLI-->>GitCore: staged changes
GitCore-->>GitManager: stagedSummary, stagedPatch
GitManager->>CodexTextGenerator: generateCommitMessage()
CodexTextGenerator->>CodexTextGenerator: Write temp schema file
CodexTextGenerator->>GitCLI: codex exec --output-schema
GitCLI-->>CodexTextGenerator: JSON response
CodexTextGenerator->>CodexTextGenerator: Parse, sanitize, cleanup
CodexTextGenerator-->>GitManager: {subject, body}
GitManager->>GitCore: commit(cwd, subject, body)
GitCore->>GitCLI: git commit -m subject -m body
GitCLI-->>GitCore: success
GitCore-->>GitManager: {commitSha}
GitManager-->>NativeApi: commit result
NativeApi-->>GitActionsControl: Update progress: commit completed
GitActionsControl->>NativeApi: git.runStackedAction(commit_push)
NativeApi->>GitManager: runStackedAction(commit_push)
GitManager->>GitCore: pushCurrentBranch(cwd)
GitCore->>GitCLI: git push -u origin branch
GitCLI-->>GitCore: success
GitCore-->>GitManager: {status: pushed, branch}
GitManager-->>NativeApi: push result
NativeApi-->>GitActionsControl: Update progress: push completed
GitActionsControl->>NativeApi: git.runStackedAction(commit_push_pr)
NativeApi->>GitManager: runStackedAction(commit_push_pr)
GitManager->>GitHubCLI: gh pr list --head branch
GitHubCLI-->>GitManager: [] (no existing PR)
GitManager->>GitCore: readRangeContext(cwd, baseBranch)
GitCore->>GitCLI: git log, git diff
GitCLI-->>GitCore: commit history, diff
GitCore-->>GitManager: rangeContext
GitManager->>CodexTextGenerator: generatePrContent()
CodexTextGenerator->>GitCLI: codex exec --output-schema
GitCLI-->>CodexTextGenerator: JSON response
CodexTextGenerator-->>GitManager: {title, body}
GitManager->>GitHubCLI: gh pr create --title --body-file
GitHubCLI-->>GitManager: PR URL
GitManager->>GitHubCLI: gh pr view --web
GitHubCLI-->>GitManager: success
GitManager-->>NativeApi: pr result
NativeApi-->>GitActionsControl: Update progress: PR created
GitActionsControl->>User: Show completion with PR link
Loading

Last reviewed commit: dd92084

@greptile-appsgreptile-appsBot 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.

14 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment threadapps/server/src/gitManager.ts

@coderabbitaicoderabbitaiBot 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.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@apps/server/src/processRunner.ts`:
- Around line 128-131: Attach an 'error' listener to the child's stdin and use
the write callback to handle possible write errors before calling end: in
processRunner.ts, before calling child.stdin.write(...) register
child.stdin.once("error", err => {/* reject/forward error or cleanup and ensure
promise rejects */}), call child.stdin.write(options.stdin, (err) => { if (err)
{ /* handle/reject/cleanup */ } child.stdin.end(); }); and remove the
unconditional child.stdin.end() so we only end after the write completes; ensure
any error handling forwards the error to the same rejection/cleanup path used by
child.once("error") for the spawned process.
🧹 Nitpick comments (2)
packages/contracts/src/git.test.ts (1)

11-71: LGTM!

The tests provide good coverage for schema validation, including whitespace trimming and nested field parsing.

Consider adding negative test cases to verify that invalid inputs are rejected (e.g., invalid action strings, missing required fields). This would strengthen the contract validation.

,

apps/server/src/codexTextGenerator.ts (1)

84-103: Consider consolidating duplicate sanitization logic.

sanitizeCommitSubject here (lines 84-95) and sanitizeCommitMessage in gitManager.ts (lines 99-110) perform nearly identical operations: extracting the first line, removing trailing periods, and truncating to 72 characters. This duplication could lead to divergent behavior over time.

Consider extracting a shared utility or having gitManager.ts rely on the already-sanitized output from CodexTextGenerator without additional sanitization.

Comment threadapps/server/src/processRunner.ts
Comment threadapps/server/src/gitManager.ts Outdated
Comment threadapps/server/src/processRunner.ts
Comment threadapps/server/src/processRunner.ts
Comment threadapps/server/src/wsServer.ts
Comment threadapps/server/src/gitManager.ts Outdated
Comment threadapps/web/src/components/ChatView.tsx Outdated
Comment threadapps/server/src/codexTextGenerator.ts Outdated
@macroscopeapp

macroscopeappBot commented Feb 12, 2026

Copy link
Copy Markdown
Contributor

Add Commit, Push, and Create PR actions by introducing GitCoreService, GitManager.runStackedAction, and a web GitActionsControl wired through WS and IPC APIs

Implements stacked git workflows across server and web: adds GitCoreService for git operations, CodexTextGenerator for commit/PR text, and GitManager orchestration; exposes git.status and git.runStackedAction over WebSocket; adds desktop bridge shell.openExternal; and introduces a GitActionsControl UI to run commit/push/PR. Terminal spawning gains multi-shell fallback and runProcess provides standardized subprocess handling. See apps/server/src/gitManager.ts, apps/server/src/git.ts, apps/server/src/codexTextGenerator.ts, and apps/web/src/components/GitActionsControl.tsx.

📍Where to Start

Start with the orchestration entrypoint GitManager.runStackedAction in apps/server/src/gitManager.ts, then review GitCoreService in apps/server/src/git.ts and the web client GitActionsControl in apps/web/src/components/GitActionsControl.tsx.


Macroscope summarized dd92084.

Co-authored-by: codex <codex@users.noreply.github.com>
Comment threadapps/server/src/gitManager.ts

@coderabbitaicoderabbitaiBot 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.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@apps/web/src/components/ChatView.tsx`:
- Around line 395-424: When a git status fetch succeeds we need to clear any
previous error so the error banner doesn't persist; inside the load async
function (the one that calls api.git.status with gitCwd) after successfully
calling setGitStatus(nextStatus) also call setGitActionError(null) (guarded by
the same !cancelled check) so successful refreshes remove stale errors; update
the useEffect's load success branch in ChatView.tsx (the load function /
useEffect that references api, gitCwd, setGitStatus, setGitActionError)
accordingly.

Comment threadapps/web/src/components/ChatView.tsx Outdated
Comment threadapps/server/src/git.ts
Comment threadapps/server/src/git.ts Outdated

@coderabbitaicoderabbitaiBot 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.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@apps/server/src/git.test.ts`:
- Around line 460-466: The test invokes git with a single-quoted remote path
which fails on Windows; update the call that runs git(tmp.path, `remote add
origin '${remote.path}'`) to wrap the path using JSON.stringify(remote.path)
(i.e., produce a double-quoted, escaped string) so Windows cmd handles spaces
correctly; modify the test where makeTmpDir()/remote and the git(...) invocation
are used (see git(tmp.path, `remote add origin ...`), initRepoWithCommit,
createGitBranch) to pass the JSON.stringify-wrapped path instead of single
quotes.
In `@apps/server/src/git.ts`:
- Around line 245-251: The current early-return treats a branch as
"skipped_up_to_date" when details.hasUpstream && details.aheadCount === 0 even
if it is behind; update the condition to also require details.behindCount === 0
so we only mark truly up-to-date branches. Change the if that checks
details.hasUpstream and details.aheadCount to: details.hasUpstream &&
details.aheadCount === 0 && details.behindCount === 0 (leaving the returned
object with branch and optional upstreamBranch unchanged).
- Around line 129-143: The timeout check must be unconditional: in
runGitOrThrow, always throw when result.timedOut by calling
normalizeGitExecutionError(args, result) (or similar) before considering
options.allowNonZeroExit; then keep the existing non-zero exit handling for
result.code when options.allowNonZeroExit is false. Update the logic in
runGitOrThrow (referencing runGitOrThrow, RunGitOptions,
options.allowNonZeroExit, result.timedOut, result.code, and
normalizeGitExecutionError) so timeouts are detected and thrown unconditionally
while allowing suppressed non-zero exit codes only when appropriate.
In `@apps/server/src/gitManager.ts`:
- Around line 402-423: Both runGh and runGhStdout currently call this.run("gh",
args, { cwd }) and can hang; add an explicit timeoutMs option to those calls.
Define a clear constant (e.g. GH_CLI_TIMEOUT_MS = 30_000) near the top of the
module and pass it into this.run as { cwd, timeoutMs: GH_CLI_TIMEOUT_MS } in
both runGh and runGhStdout so gh CLI invocations time out predictably. Ensure
the constant is used in both functions and adjust any types if needed to match
ProcessRunOptions.
🧹 Nitpick comments (1)
apps/server/src/gitManager.ts (1)

175-183: If no commit is created, skip push/PR to avoid empty actions.

When runCommitStep returns skipped_no_changes, the current flow still pushes and can attempt PR creation. That can create confusing “no‑op” PRs or unnecessary network calls.

Comment threadapps/server/src/git.test.ts
Comment threadapps/server/src/git.ts
Comment threadapps/server/src/git.ts Outdated
Comment threadapps/server/src/gitManager.ts
@juliusmarmingejuliusmarminge changed the title Add stacked GitHub action workflowGithubFeb 12, 2026
@juliusmarmingejuliusmarminge changed the title Githubfeat: Commit, Push and Create PR actionsFeb 12, 2026
Co-authored-by: codex <codex@users.noreply.github.com>
Comment on lines +123 to +128
const fail = (error: Error): void => {
child.kill("SIGTERM");
finalize(() => {
reject(error);
});
};

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.

🟡 Medium

src/processRunner.ts:123 The fail function clears the timeout that would escalate to SIGKILL, so if the child ignores SIGTERM, the parent hangs forever. Consider adding a force-kill timer in fail similar to the timeout logic (lines 108-110).

- const fail = (error: Error): void => {- child.kill("SIGTERM");- finalize(() => {- reject(error);- });+ const fail = (error: Error): void => {+ child.kill("SIGTERM");+ const killTimer = setTimeout(() => {+ child.kill("SIGKILL");+ }, 1_000);+ finalize(() => {+ clearTimeout(killTimer);+ reject(error);+ });

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/processRunner.ts around lines 123-128:
The `fail` function clears the timeout that would escalate to `SIGKILL`, so if the child ignores `SIGTERM`, the parent hangs forever. Consider adding a force-kill timer in `fail` similar to the timeout logic (lines 108-110).

Comment threadapps/server/src/processRunner.ts Outdated
stream: "stdout" | "stderr",
chunk: Buffer | string,
): Error | null => {
const text = chunk.toString();

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.

🟢 Low

src/processRunner.ts:134 Using chunk.toString() per Buffer can split multi‑byte UTF‑8 and corrupt text; consider StringDecoder to assemble stdout/stderr safely.

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/processRunner.ts around line 134:
Using `chunk.toString()` per `Buffer` can split multi‑byte UTF‑8 and corrupt text; consider `StringDecoder` to assemble `stdout`/`stderr` safely.

@coderabbitaicoderabbitaiBot 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/server/src/git.ts (1)

50-86: ⚠️ Potential issue | 🟠 Major

Add output caps to avoid unbounded git command buffering.
runGit accumulates stdout/stderr without limits; large diffs can spike memory and make behavior unpredictable under load. Consider enforcing a max buffer (similar to processRunner) or streaming to disk with a safe cap.

🔧 Suggested fix (bounded output)
+const DEFAULT_GIT_MAX_BUFFER_BYTES = 8 * 1024 * 1024;+-function runGit(args: readonly string[], cwd: string, timeoutMs = 30_000): Promise<TerminalCommandResult> {+function runGit(+ args: readonly string[],+ cwd: string,+ timeoutMs = 30_000,+ maxBufferBytes = DEFAULT_GIT_MAX_BUFFER_BYTES,+): Promise<TerminalCommandResult> {
return new Promise((resolve, reject) => {
const child = spawn("git", args, {
cwd,
env: process.env,
stdio: ["ignore", "pipe", "pipe"],
});
let stdout = "";
let stderr = "";
let timedOut = false;
+ let stdoutBytes = 0;+ let stderrBytes = 0;+ let settled = false;
const timeout = setTimeout(() => {
timedOut = true;
child.kill("SIGTERM");
setTimeout(() => {
if (!child.killed) child.kill("SIGKILL");
}, 1_000).unref();
}, timeoutMs);
+ const fail = (error: Error) => {+ if (settled) return;+ settled = true;+ clearTimeout(timeout);+ child.kill("SIGTERM");+ reject(error);+ };+
child.stdout?.on("data", (chunk: Buffer) => {
- stdout += chunk.toString();+ const text = chunk.toString();+ stdout += text;+ stdoutBytes += Buffer.byteLength(text);+ if (stdoutBytes > maxBufferBytes) {+ fail(+ new Error(+ `${commandLabel(args)} exceeded stdout buffer limit (${maxBufferBytes} bytes).`,+ ),+ );+ }
});
child.stderr?.on("data", (chunk: Buffer) => {
- stderr += chunk.toString();+ const text = chunk.toString();+ stderr += text;+ stderrBytes += Buffer.byteLength(text);+ if (stderrBytes > maxBufferBytes) {+ fail(+ new Error(+ `${commandLabel(args)} exceeded stderr buffer limit (${maxBufferBytes} bytes).`,+ ),+ );+ }
});
child.on("error", (error) => {
clearTimeout(timeout);
reject(error);
});
child.on("close", (code, signal) => {
+ if (settled) return;+ settled = true;
clearTimeout(timeout);
resolve({ stdout, stderr, code: code ?? null, signal: signal ?? null, timedOut });
});
});
}

As per coding guidelines: Maintain predictable behavior under load and during failures (session restarts, reconnects, partial streams).

Comment threadapps/web/src/components/ChatView.tsx Outdated
Co-authored-by: codex <codex@users.noreply.github.com>
await runGitOrThrow(cwd, args, { allowNonZeroExit });
}

async gitStdout(cwd: string, args: readonly string[], allowNonZeroExit = false): Promise<string> {

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.

🟡 Medium

src/git.ts:359gitStdout discards stderr even on success, so the truncation warning from runGit is lost. Consider checking if output was truncated and either throwing an error or returning a flag so callers know the data is incomplete.

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/git.ts around line 359:
`gitStdout` discards `stderr` even on success, so the truncation warning from `runGit` is lost. Consider checking if output was truncated and either throwing an error or returning a flag so callers know the data is incomplete.

},
);

const rawOutput = (await fs.readFile(outputPath, "utf8")).trim();

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.

🟠 High

src/codexTextGenerator.ts:145fs.readFile at line 145 has no size limit, unlike the maxBufferBytes guard on stdout/stderr in runProcess. Consider adding a file size check (via fs.stat) before reading to prevent OOM if codex produces unexpectedly large output.

- const rawOutput = (await fs.readFile(outputPath, "utf8")).trim();+ const MAX_OUTPUT_BYTES = 8 * 1024 * 1024;+ const stat = await fs.stat(outputPath);+ if (stat.size > MAX_OUTPUT_BYTES) {+ throw new Error(`Codex output exceeded size limit (${MAX_OUTPUT_BYTES} bytes).`);+ }+ const rawOutput = (await fs.readFile(outputPath, "utf8")).trim();

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/codexTextGenerator.ts around line 145:
`fs.readFile` at line 145 has no size limit, unlike the `maxBufferBytes` guard on stdout/stderr in `runProcess`. Consider adding a file size check (via `fs.stat`) before reading to prevent OOM if `codex` produces unexpectedly large output.

- include open PR metadata in git status with graceful `gh` failure handling
- centralize git command execution in `GitCoreService` via `runProcess` with truncation support
- add PTY spawn-helper permission fixes, shell fallback retries, and tests for new behavior
}

async createWorktree(input: GitCreateWorktreeInput): Promise<GitCreateWorktreeResult> {
const sanitizedBranch = input.newBranch.replace(/\//g, "-");

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.

🟡 Medium

src/git.ts:382 Default worktreePath can collide across repos and branches (sanitized names and shared basenames). Suggest deriving a unique, deterministic path (e.g., hash of full repo path + branch) or document if intentional.

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/git.ts around line 382:
Default `worktreePath` can collide across repos and branches (sanitized names and shared basenames). Suggest deriving a unique, deterministic path (e.g., hash of full repo path + branch) or document if intentional.

args.push("-m", trimmedBody);
}
await this.git(cwd, args);
const commitSha = trimStdout(await this.gitStdout(cwd, ["rev-parse", "HEAD"]));

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.

🟢 Low

src/git.ts:256 Race condition: another commit between git commit and git rev-parse HEAD could return the wrong SHA. Consider using git rev-parse HEAD output from the commit command itself, or use git commit --porcelain to get the SHA atomically.

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/git.ts around line 256:
Race condition: another commit between `git commit` and `git rev-parse HEAD` could return the wrong SHA. Consider using `git rev-parse HEAD` output from the commit command itself, or use `git commit --porcelain` to get the SHA atomically.

const worktreeMap = new Map<string, string>();
if (worktreeList.code === 0) {
let currentPath: string | null = null;
for (const line of worktreeList.stdout.split("\n")) {

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.

🟡 Medium

src/git.ts:347 On Windows, split("\n") leaves trailing \r in paths, causing fs.existsSync to fail. Consider using split(/\r?\n/) instead.

Suggested change
for(constlineofworktreeList.stdout.split("\n")){
for(constlineofworktreeList.stdout.split(/\r?\n/)){

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/git.ts around line 347:
On Windows, `split("\n")` leaves trailing `\r` in paths, causing `fs.existsSync` to fail. Consider using `split(/\r?\n/)` instead.

}

async removeWorktree(input: GitRemoveWorktreeInput): Promise<void> {
await executeGit(input.cwd, ["worktree", "remove", input.path], {

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.

🟢 Low

src/git.ts:402 Consider adding -- before input.path to prevent paths starting with - from being interpreted as git options.

Suggested change
awaitexecuteGit(input.cwd,["worktree","remove",input.path],{
awaitexecuteGit(input.cwd,["worktree","remove","--",input.path],{

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/server/src/git.ts around line 402:
Consider adding `--` before `input.path` to prevent paths starting with `-` from being interpreted as git options.

- Replace custom inline SVGs with Lucide commit, push, and GitHub icons
- Keep git action behavior unchanged while applying minor style cleanup
Comment threadapps/web/src/components/ChatView.tsx Outdated
juliusmarmingeand others added 3 commits February 12, 2026 12:22
- Add a Git action confirmation modal with live commit/push/PR progress states
- Accept optional `commitMessage` input and skip AI message generation when provided
- Expand server and contracts tests for custom commit message handling
Co-authored-by: codex <codex@users.noreply.github.com>
- Move git menu, modal, and stacked action logic out of `ChatView`
- Render a new `GitActionsControl` wired with `api` and `gitCwd`
- Split Git actions into context-aware Commit, Push, and PR menu items
- Add modal action selection with clearer availability and disabled-state guidance
- Migrate git status and immediate actions to React Query and add a custom GitHub icon
Co-authored-by: codex <codex@users.noreply.github.com>
@coderabbitaicoderabbitaiBot mentioned this pull request Feb 15, 2026
jjalangtry pushed a commit to jjalangtry/t3code that referenced this pull request Mar 16, 2026
dcherrera pushed a commit to dcherrera/t3code that referenced this pull request Jul 7, 2026
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 16, 2026
Makes declarative settings real: a plugin declares a schema and the HOST renders the
form. The plugin ships no form, no RPC, no storage.
The loader now synthesises a settings page from `definition.settings`, so the
declaration is no longer inert (the previous commit shipped the declaration with
nothing consuming it). Generated before register(), so a plugin declaring both gets
its generated page first, then its own; the page id is fixed so a plugin's own
registerSettingsPage cannot shadow it.
PluginSettingsPage wires the concurrency story end to end: the draft carries a
revision, every save sends it back as expectedRevision, and a stale save is rejected
by the server rather than silently clobbering another tab. The server's message is
surfaced verbatim because "does not match the schema" and "changed elsewhere" need
different actions from the user.
The form opens on incompatible stored data and says so, rather than refusing — that
is exactly when the user needs it to repair the values, and the stored data is
preserved until a valid save (Sol spec review MUST pingdotgg#13's recovery half).
This also makes the previously-vacuous test assertable. It now asserts the generated
page appears, and is verified deletion-sensitive: removing the synthesis fails both
new tests. `declared: false` (no schema, or plugin disabled) renders an explanation
rather than an empty form.
Gates: typecheck 0 errors; web 1319 passed; server 1672 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 16, 2026
Sol spec review MUST pingdotgg#13 (detection half). The store recorded a schema fingerprint
from the first commit but nothing ever read it back, so an upgrade that changed a
plugin's schema under already-stored values went undetected — the values were read
as if they still matched, which is exactly the silent misread the fingerprint exists
to prevent.
readDraft now surfaces the fingerprint that produced the stored values, and the
settings RPC compares it against the plugin's CURRENT schema. A mismatch marks the
draft incompatible, which the settings page already renders as "these settings need
attention" with the form open for repair.
The stored data is preserved on mismatch, not discarded: the user may need to read
the old values to reconstruct the new ones, and a plugin that briefly downgrades
should not lose its config. Recovery is a valid save, which rewrites both the values
and the fingerprint.
Tests verified deletion-sensitive: returning a null fingerprint from readDraft fails
both fingerprint tests.
Remaining for MUST pingdotgg#13: hostApi.settings.get should fail InvalidStored on drift
rather than relying on decode alone. The decode already fails for an incompatible
shape in the common case; the fingerprint makes it explicit. Tracked, not claimed.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 16, 2026
Sol spec review MUST pingdotgg#13, plugin-read half. hostApi.settings.get now rejects stored
values whose fingerprint does not match the plugin's current schema, instead of
relying on decode to notice.
Decode is not sufficient and that is the whole point: a widening change (adding an
optional field, relaxing a filter) still decodes cleanly, so the plugin would
silently run on values written for a shape it no longer declares. Comparing the
fingerprint makes the mismatch the fact being checked rather than a side effect of
decoding. The error carries no values — only the plugin id.
KNOWN GAP, stated rather than implied: this path has no test. I wrote one with a
fixture that probes settings.get from a service and records the Exit tag to its data
dir; the probe did not get scheduled within the test's wait, and the "<probe never
ran>" guard I built into it caught that rather than letting it pass vacuously against
whatever I asserted. Rather than ship a test that cannot reliably observe the
behaviour — or one that asserts only "the plugin activated", which would pass with
this check deleted — the tests are removed and the gap recorded.
Store-level fingerprint behaviour IS covered and verified deletion-sensitive
(PluginSettingsStore.test.ts); what is untested is specifically the capability read
path rejecting on drift. Needs a fixture harness that can await a forked service.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 16, 2026
Makes declarative settings real: a plugin declares a schema and the HOST renders the
form. The plugin ships no form, no RPC, no storage.
The loader now synthesises a settings page from `definition.settings`, so the
declaration is no longer inert (the previous commit shipped the declaration with
nothing consuming it). Generated before register(), so a plugin declaring both gets
its generated page first, then its own; the page id is fixed so a plugin's own
registerSettingsPage cannot shadow it.
PluginSettingsPage wires the concurrency story end to end: the draft carries a
revision, every save sends it back as expectedRevision, and a stale save is rejected
by the server rather than silently clobbering another tab. The server's message is
surfaced verbatim because "does not match the schema" and "changed elsewhere" need
different actions from the user.
The form opens on incompatible stored data and says so, rather than refusing — that
is exactly when the user needs it to repair the values, and the stored data is
preserved until a valid save (Sol spec review MUST pingdotgg#13's recovery half).
This also makes the previously-vacuous test assertable. It now asserts the generated
page appears, and is verified deletion-sensitive: removing the synthesis fails both
new tests. `declared: false` (no schema, or plugin disabled) renders an explanation
rather than an empty form.
Gates: typecheck 0 errors; web 1319 passed; server 1672 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 16, 2026
Sol spec review MUST pingdotgg#13 (detection half). The store recorded a schema fingerprint
from the first commit but nothing ever read it back, so an upgrade that changed a
plugin's schema under already-stored values went undetected — the values were read
as if they still matched, which is exactly the silent misread the fingerprint exists
to prevent.
readDraft now surfaces the fingerprint that produced the stored values, and the
settings RPC compares it against the plugin's CURRENT schema. A mismatch marks the
draft incompatible, which the settings page already renders as "these settings need
attention" with the form open for repair.
The stored data is preserved on mismatch, not discarded: the user may need to read
the old values to reconstruct the new ones, and a plugin that briefly downgrades
should not lose its config. Recovery is a valid save, which rewrites both the values
and the fingerprint.
Tests verified deletion-sensitive: returning a null fingerprint from readDraft fails
both fingerprint tests.
Remaining for MUST pingdotgg#13: hostApi.settings.get should fail InvalidStored on drift
rather than relying on decode alone. The decode already fails for an incompatible
shape in the common case; the fingerprint makes it explicit. Tracked, not claimed.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 16, 2026
Sol spec review MUST pingdotgg#13, plugin-read half. hostApi.settings.get now rejects stored
values whose fingerprint does not match the plugin's current schema, instead of
relying on decode to notice.
Decode is not sufficient and that is the whole point: a widening change (adding an
optional field, relaxing a filter) still decodes cleanly, so the plugin would
silently run on values written for a shape it no longer declares. Comparing the
fingerprint makes the mismatch the fact being checked rather than a side effect of
decoding. The error carries no values — only the plugin id.
KNOWN GAP, stated rather than implied: this path has no test. I wrote one with a
fixture that probes settings.get from a service and records the Exit tag to its data
dir; the probe did not get scheduled within the test's wait, and the "<probe never
ran>" guard I built into it caught that rather than letting it pass vacuously against
whatever I asserted. Rather than ship a test that cannot reliably observe the
behaviour — or one that asserts only "the plugin activated", which would pass with
this check deleted — the tests are removed and the gap recorded.
Store-level fingerprint behaviour IS covered and verified deletion-sensitive
(PluginSettingsStore.test.ts); what is untested is specifically the capability read
path rejecting on drift. Needs a fixture harness that can await a forked service.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 17, 2026
Makes declarative settings real: a plugin declares a schema and the HOST renders the
form. The plugin ships no form, no RPC, no storage.
The loader now synthesises a settings page from `definition.settings`, so the
declaration is no longer inert (the previous commit shipped the declaration with
nothing consuming it). Generated before register(), so a plugin declaring both gets
its generated page first, then its own; the page id is fixed so a plugin's own
registerSettingsPage cannot shadow it.
PluginSettingsPage wires the concurrency story end to end: the draft carries a
revision, every save sends it back as expectedRevision, and a stale save is rejected
by the server rather than silently clobbering another tab. The server's message is
surfaced verbatim because "does not match the schema" and "changed elsewhere" need
different actions from the user.
The form opens on incompatible stored data and says so, rather than refusing — that
is exactly when the user needs it to repair the values, and the stored data is
preserved until a valid save (Sol spec review MUST pingdotgg#13's recovery half).
This also makes the previously-vacuous test assertable. It now asserts the generated
page appears, and is verified deletion-sensitive: removing the synthesis fails both
new tests. `declared: false` (no schema, or plugin disabled) renders an explanation
rather than an empty form.
Gates: typecheck 0 errors; web 1319 passed; server 1672 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 17, 2026
Sol spec review MUST pingdotgg#13 (detection half). The store recorded a schema fingerprint
from the first commit but nothing ever read it back, so an upgrade that changed a
plugin's schema under already-stored values went undetected — the values were read
as if they still matched, which is exactly the silent misread the fingerprint exists
to prevent.
readDraft now surfaces the fingerprint that produced the stored values, and the
settings RPC compares it against the plugin's CURRENT schema. A mismatch marks the
draft incompatible, which the settings page already renders as "these settings need
attention" with the form open for repair.
The stored data is preserved on mismatch, not discarded: the user may need to read
the old values to reconstruct the new ones, and a plugin that briefly downgrades
should not lose its config. Recovery is a valid save, which rewrites both the values
and the fingerprint.
Tests verified deletion-sensitive: returning a null fingerprint from readDraft fails
both fingerprint tests.
Remaining for MUST pingdotgg#13: hostApi.settings.get should fail InvalidStored on drift
rather than relying on decode alone. The decode already fails for an incompatible
shape in the common case; the fingerprint makes it explicit. Tracked, not claimed.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 17, 2026
Sol spec review MUST pingdotgg#13, plugin-read half. hostApi.settings.get now rejects stored
values whose fingerprint does not match the plugin's current schema, instead of
relying on decode to notice.
Decode is not sufficient and that is the whole point: a widening change (adding an
optional field, relaxing a filter) still decodes cleanly, so the plugin would
silently run on values written for a shape it no longer declares. Comparing the
fingerprint makes the mismatch the fact being checked rather than a side effect of
decoding. The error carries no values — only the plugin id.
KNOWN GAP, stated rather than implied: this path has no test. I wrote one with a
fixture that probes settings.get from a service and records the Exit tag to its data
dir; the probe did not get scheduled within the test's wait, and the "<probe never
ran>" guard I built into it caught that rather than letting it pass vacuously against
whatever I asserted. Rather than ship a test that cannot reliably observe the
behaviour — or one that asserts only "the plugin activated", which would pass with
this check deleted — the tests are removed and the gap recorded.
Store-level fingerprint behaviour IS covered and verified deletion-sensitive
(PluginSettingsStore.test.ts); what is untested is specifically the capability read
path rejecting on drift. Needs a fixture harness that can await a forked service.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 30, 2026
Makes declarative settings real: a plugin declares a schema and the HOST renders the
form. The plugin ships no form, no RPC, no storage.
The loader now synthesises a settings page from `definition.settings`, so the
declaration is no longer inert (the previous commit shipped the declaration with
nothing consuming it). Generated before register(), so a plugin declaring both gets
its generated page first, then its own; the page id is fixed so a plugin's own
registerSettingsPage cannot shadow it.
PluginSettingsPage wires the concurrency story end to end: the draft carries a
revision, every save sends it back as expectedRevision, and a stale save is rejected
by the server rather than silently clobbering another tab. The server's message is
surfaced verbatim because "does not match the schema" and "changed elsewhere" need
different actions from the user.
The form opens on incompatible stored data and says so, rather than refusing — that
is exactly when the user needs it to repair the values, and the stored data is
preserved until a valid save (Sol spec review MUST pingdotgg#13's recovery half).
This also makes the previously-vacuous test assertable. It now asserts the generated
page appears, and is verified deletion-sensitive: removing the synthesis fails both
new tests. `declared: false` (no schema, or plugin disabled) renders an explanation
rather than an empty form.
Gates: typecheck 0 errors; web 1319 passed; server 1672 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 30, 2026
Sol spec review MUST pingdotgg#13 (detection half). The store recorded a schema fingerprint
from the first commit but nothing ever read it back, so an upgrade that changed a
plugin's schema under already-stored values went undetected — the values were read
as if they still matched, which is exactly the silent misread the fingerprint exists
to prevent.
readDraft now surfaces the fingerprint that produced the stored values, and the
settings RPC compares it against the plugin's CURRENT schema. A mismatch marks the
draft incompatible, which the settings page already renders as "these settings need
attention" with the form open for repair.
The stored data is preserved on mismatch, not discarded: the user may need to read
the old values to reconstruct the new ones, and a plugin that briefly downgrades
should not lose its config. Recovery is a valid save, which rewrites both the values
and the fingerprint.
Tests verified deletion-sensitive: returning a null fingerprint from readDraft fails
both fingerprint tests.
Remaining for MUST pingdotgg#13: hostApi.settings.get should fail InvalidStored on drift
rather than relying on decode alone. The decode already fails for an incompatible
shape in the common case; the fingerprint makes it explicit. Tracked, not claimed.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 30, 2026
Sol spec review MUST pingdotgg#13, plugin-read half. hostApi.settings.get now rejects stored
values whose fingerprint does not match the plugin's current schema, instead of
relying on decode to notice.
Decode is not sufficient and that is the whole point: a widening change (adding an
optional field, relaxing a filter) still decodes cleanly, so the plugin would
silently run on values written for a shape it no longer declares. Comparing the
fingerprint makes the mismatch the fact being checked rather than a side effect of
decoding. The error carries no values — only the plugin id.
KNOWN GAP, stated rather than implied: this path has no test. I wrote one with a
fixture that probes settings.get from a service and records the Exit tag to its data
dir; the probe did not get scheduled within the test's wait, and the "<probe never
ran>" guard I built into it caught that rather than letting it pass vacuously against
whatever I asserted. Rather than ship a test that cannot reliably observe the
behaviour — or one that asserts only "the plugin activated", which would pass with
this check deleted — the tests are removed and the gap recorded.
Store-level fingerprint behaviour IS covered and verified deletion-sensitive
(PluginSettingsStore.test.ts); what is untested is specifically the capability read
path rejecting on drift. Needs a fixture harness that can await a forked service.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 30, 2026
Makes declarative settings real: a plugin declares a schema and the HOST renders the
form. The plugin ships no form, no RPC, no storage.
The loader now synthesises a settings page from `definition.settings`, so the
declaration is no longer inert (the previous commit shipped the declaration with
nothing consuming it). Generated before register(), so a plugin declaring both gets
its generated page first, then its own; the page id is fixed so a plugin's own
registerSettingsPage cannot shadow it.
PluginSettingsPage wires the concurrency story end to end: the draft carries a
revision, every save sends it back as expectedRevision, and a stale save is rejected
by the server rather than silently clobbering another tab. The server's message is
surfaced verbatim because "does not match the schema" and "changed elsewhere" need
different actions from the user.
The form opens on incompatible stored data and says so, rather than refusing — that
is exactly when the user needs it to repair the values, and the stored data is
preserved until a valid save (Sol spec review MUST pingdotgg#13's recovery half).
This also makes the previously-vacuous test assertable. It now asserts the generated
page appears, and is verified deletion-sensitive: removing the synthesis fails both
new tests. `declared: false` (no schema, or plugin disabled) renders an explanation
rather than an empty form.
Gates: typecheck 0 errors; web 1319 passed; server 1672 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 30, 2026
Sol spec review MUST pingdotgg#13 (detection half). The store recorded a schema fingerprint
from the first commit but nothing ever read it back, so an upgrade that changed a
plugin's schema under already-stored values went undetected — the values were read
as if they still matched, which is exactly the silent misread the fingerprint exists
to prevent.
readDraft now surfaces the fingerprint that produced the stored values, and the
settings RPC compares it against the plugin's CURRENT schema. A mismatch marks the
draft incompatible, which the settings page already renders as "these settings need
attention" with the form open for repair.
The stored data is preserved on mismatch, not discarded: the user may need to read
the old values to reconstruct the new ones, and a plugin that briefly downgrades
should not lose its config. Recovery is a valid save, which rewrites both the values
and the fingerprint.
Tests verified deletion-sensitive: returning a null fingerprint from readDraft fails
both fingerprint tests.
Remaining for MUST pingdotgg#13: hostApi.settings.get should fail InvalidStored on drift
rather than relying on decode alone. The decode already fails for an incompatible
shape in the common case; the fingerprint makes it explicit. Tracked, not claimed.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 30, 2026
Sol spec review MUST pingdotgg#13, plugin-read half. hostApi.settings.get now rejects stored
values whose fingerprint does not match the plugin's current schema, instead of
relying on decode to notice.
Decode is not sufficient and that is the whole point: a widening change (adding an
optional field, relaxing a filter) still decodes cleanly, so the plugin would
silently run on values written for a shape it no longer declares. Comparing the
fingerprint makes the mismatch the fact being checked rather than a side effect of
decoding. The error carries no values — only the plugin id.
KNOWN GAP, stated rather than implied: this path has no test. I wrote one with a
fixture that probes settings.get from a service and records the Exit tag to its data
dir; the probe did not get scheduled within the test's wait, and the "<probe never
ran>" guard I built into it caught that rather than letting it pass vacuously against
whatever I asserted. Rather than ship a test that cannot reliably observe the
behaviour — or one that asserts only "the plugin activated", which would pass with
this check deleted — the tests are removed and the gap recorded.
Store-level fingerprint behaviour IS covered and verified deletion-sensitive
(PluginSettingsStore.test.ts); what is untested is specifically the capability read
path rejecting on drift. Needs a fixture harness that can await a forked service.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 31, 2026
Makes declarative settings real: a plugin declares a schema and the HOST renders the
form. The plugin ships no form, no RPC, no storage.
The loader now synthesises a settings page from `definition.settings`, so the
declaration is no longer inert (the previous commit shipped the declaration with
nothing consuming it). Generated before register(), so a plugin declaring both gets
its generated page first, then its own; the page id is fixed so a plugin's own
registerSettingsPage cannot shadow it.
PluginSettingsPage wires the concurrency story end to end: the draft carries a
revision, every save sends it back as expectedRevision, and a stale save is rejected
by the server rather than silently clobbering another tab. The server's message is
surfaced verbatim because "does not match the schema" and "changed elsewhere" need
different actions from the user.
The form opens on incompatible stored data and says so, rather than refusing — that
is exactly when the user needs it to repair the values, and the stored data is
preserved until a valid save (Sol spec review MUST pingdotgg#13's recovery half).
This also makes the previously-vacuous test assertable. It now asserts the generated
page appears, and is verified deletion-sensitive: removing the synthesis fails both
new tests. `declared: false` (no schema, or plugin disabled) renders an explanation
rather than an empty form.
Gates: typecheck 0 errors; web 1319 passed; server 1672 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 31, 2026
Sol spec review MUST pingdotgg#13 (detection half). The store recorded a schema fingerprint
from the first commit but nothing ever read it back, so an upgrade that changed a
plugin's schema under already-stored values went undetected — the values were read
as if they still matched, which is exactly the silent misread the fingerprint exists
to prevent.
readDraft now surfaces the fingerprint that produced the stored values, and the
settings RPC compares it against the plugin's CURRENT schema. A mismatch marks the
draft incompatible, which the settings page already renders as "these settings need
attention" with the form open for repair.
The stored data is preserved on mismatch, not discarded: the user may need to read
the old values to reconstruct the new ones, and a plugin that briefly downgrades
should not lose its config. Recovery is a valid save, which rewrites both the values
and the fingerprint.
Tests verified deletion-sensitive: returning a null fingerprint from readDraft fails
both fingerprint tests.
Remaining for MUST pingdotgg#13: hostApi.settings.get should fail InvalidStored on drift
rather than relying on decode alone. The decode already fails for an incompatible
shape in the common case; the fingerprint makes it explicit. Tracked, not claimed.
Gates: typecheck 0 errors; server 1675 passed.
ccdwyer added a commit to ccdwyer/t3code that referenced this pull request Jul 31, 2026
Sol spec review MUST pingdotgg#13, plugin-read half. hostApi.settings.get now rejects stored
values whose fingerprint does not match the plugin's current schema, instead of
relying on decode to notice.
Decode is not sufficient and that is the whole point: a widening change (adding an
optional field, relaxing a filter) still decodes cleanly, so the plugin would
silently run on values written for a shape it no longer declares. Comparing the
fingerprint makes the mismatch the fact being checked rather than a side effect of
decoding. The error carries no values — only the plugin id.
KNOWN GAP, stated rather than implied: this path has no test. I wrote one with a
fixture that probes settings.get from a service and records the Exit tag to its data
dir; the probe did not get scheduled within the test's wait, and the "<probe never
ran>" guard I built into it caught that rather than letting it pass vacuously against
whatever I asserted. Rather than ship a test that cannot reliably observe the
behaviour — or one that asserts only "the plugin activated", which would pass with
this check deleted — the tests are removed and the gap recorded.
Store-level fingerprint behaviour IS covered and verified deletion-sensitive
(PluginSettingsStore.test.ts); what is untested is specifically the capability read
path rejecting on drift. Needs a fixture harness that can await a forked service.
Gates: typecheck 0 errors; server 1675 passed.
aorwall added a commit to aorwall/t3code that referenced this pull request Aug 2, 2026
* docs: spec for preview servers in the T3 Code web app
Approved spec covering the Servers right-panel view, Moatless-owned preview
tabs backed by Redis, and an iframe renderer that lets the existing browser
panel work outside Electron.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat: add the servers.* contract group
Three reads over the servers a thread's environment declares: list them, watch
their status, and follow one's log. Field names are taken verbatim from the
host's own server record so the server side is a rename-free serialization.
Nothing here starts, stops or reconfigures a server; those methods are
specified elsewhere and deliberately out of scope.
The preview module docstring said the preview is desktop-only and that the
desktop renderer mediates. Both stop being true in the same change, so the
docstring now describes the surface by capability rather than by client.
* feat: carry the servers.* group through the client runtime and reference server
Client-side atoms for the three reads, with the log subscription folding
lines into a bounded buffer so a remounted panel keeps what it received
rather than starting over.
The reference server declares no thread servers - it runs threads on the
machine it is on - so it answers the list empty and both subscriptions
silent. A hosted environment answers the same three methods with real data.
Also splits the preview runtime capability in two. One boolean was answering
both "can this runtime show a page" and "can this runtime read the page it
shows", which are the same question only on the desktop app.
* feat: host preview pages in a sandboxed frame on the web
The web app could not show a task's page at all. It can now: one frame per
open tab, mounted at the app root so it survives the panel collapsing, and
positioned by the same surface rect the desktop webview uses.
The frame is driven and never read. Navigation writes to the server and the
frame follows; refresh replaces the element because a cross-origin frame has
no reload and reassigning src would grow the parent's history; back and
forward are absent rather than dead.
Two things a frame cannot report, the panel says from elsewhere. A page that
is not there is explained by what its server says about itself, which is
better than a net error - "installing" is an answer no net error carries. A
page that renders nothing while its server says it is running gets a hint
offered as a hint, because a frame-ancestors refusal and the preview host's
own 401 look identical from outside and neither fires an event.
* feat: add a Servers view to the right panel
One row per server the thread's environment declares, with its status kept
current by the subscription, its log on demand, and an Open that hands the
URL to a browser tab.
Everything in it is a read - a row that says failed offers its log and no
button. Restarting a server is a write and lands elsewhere.
The right panel's persisted state moves to version 8 for the new surface
kind, and its migration now drops surfaces whose kind this build does not
know. That is what makes the version bump safe to downgrade away from: the
rest of the workspace survives and only the unknown tab is lost.
* test: cover the browser preview surface and the servers view
Two products share these schemas and no test process, so the seam is a set
of real Moatless responses checked in here and decoded by the schemas
themselves. When its projection changes the fixture changes with it in one
commit, and the decode test is what fails if the two drift.
The rest covers what the change actually promises: the capability answers
three runtimes, the chrome row omits controls rather than disabling them,
the frame re-keys instead of reassigning src, exactly one browser host
renders, and the panel state survives the version bump while dropping a kind
this build does not know.
* fix: state the environment's absence rather than implying it from a row
The never-provisioned fixture claimed a server is listed as `stopped` with
no URL. It is not. Moatless resolves status config-first from a NotFound
pod, which falls back to `starting` with the ingress URL the port will have
— so the panel showed `starting` forever for an environment that does not
exist, and offered an Open button pointing at a 502.
The fixture now carries what the backend produces, and the panel states the
environment's own status above the list instead of leaving it to be inferred
from rows that cannot say it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: track the fork's own delta, not just what upstream does to it
The policy could answer "who wins this conflict" but not "what did we change",
and the second is the question you are actually asking when git stops on a line
you do not recognise. The path table only ever listed files we expected to fight
over — a much smaller set than the files we changed. The whole thread-servers
group, around forty files, appeared nowhere.
§3 is that missing list, and a fifth hazard in the preamble names the failure it
prevents: a conflict resolved toward upstream because nobody could tell our line
was deliberate. Two consequences: the checklist asks for a row in the same commit
as the change rather than at the next merge, and reading the inventory is now a
step in resolving a conflict rather than something to remember.
Also adds path-policy rows for the highest-risk of those files —
`apps/server/src/ws.ts` and `RpcAuthorization.ts`, the only upstream server files
the fork touches, where the resolution is to take theirs and re-add three
`servers.*` entries.
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
aorwall pushed a commit to aorwall/t3code that referenced this pull request Aug 12, 2026
The 2026-08-08 upstream merge took 89 minutes. Most of that was not conflict
resolution; it was performing checks that existed only as prose in
docs/fork/upstream-merge-inventory.md, one at a time, and rediscovering two
things the doc could have told me before I started.
Five changes, in the order they pay off.
**The inventory is data now.** docs/fork/inventory.json holds the fork-owned
concerns, path policy, fork inventory, tripwires, deliberately deleted upstream
paths, off-repository state and convergence entries. The markdown keeps only
what a check cannot hold — the four re-application deltas, where the
unsupported-method set comes from, and the reasoning behind the path policy
rule — and drops from 784 lines to 243.
**The checks run.** Five dependency-free scripts under the skill:
- `preflight.mjs` — the range, stale entries, the owned-concern sweep, and the
conflict forecast: every file both sides touched, grouped by the verdict that
resolves it, before the merge starts.
- `inventory-check.mjs` — every inventory path read back out of upstream/main.
This is the one that matters: last merge, upstream moved SidebarV2's content
into Sidebar.tsx and Sidebar.tsx's into LegacySidebar.tsx. Git cannot see a
content swap as a rename — it is a delete paired with a modify, which no -M
threshold detects — so it surfaced as a modify/delete conflict mid-merge.
Run against the pre-merge tree, this check names it in about a second.
- `tripwires.mjs`, `unsupported-methods.mjs`, `verify.mjs`.
Running inventory-check against the current tree immediately found three dead
path-policy entries that had been faithfully transcribed forward through
several merges, for directories deleted in pingdotgg#13 and pingdotgg#26.
**Verification is one command, earlier.** `verify.mjs` runs tripwires, the
unsupported-method derivation, format, lint, types and tests — and does not
stop at the first failure, so a formatting nit no longer hides the type errors
behind it. It raises the heap the web suite needs, whose failure mode is
otherwise an exit 137 that reads like a real test failure. The skill now runs
it *before* the documentation steps, because its output is their input.
**Counts come out of prose.** Both snapshots the last merge relied on were
stale. "38 of 87 methods" and "Clerk 4 / pairing 73 / session bootstrap 9" are
replaced by the command that derives them.
**The fork test no longer hard-codes upstream paths.** features.test.ts read
`../components/Sidebar.tsx?raw`; when upstream renamed that file the whole
suite failed to build, at a module path, saying nothing about the gate. It now
reads the guarded files out of inventory.json and reaches them through
import.meta.glob, so a rename fails as a named assertion that says which entry
to re-point — and the test and the merge scripts read the same guards, so they
cannot drift apart.
Verified: fmt, lint, typecheck and 1908 tests pass. `tripwires.mjs` reports one
finding, which is genuine and already tracked — thread-transfer-report.yml went
active on GitHub when the merge branch was pushed and still needs disabling.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
NeilTheFisher pushed a commit to NeilTheFisher/t3code that referenced this pull request Aug 18, 2026
AFLabAI added a commit to AFLabAI/t3code that referenced this pull request Sep 2, 2026
Increase diagnostic visibility by appending complete vp check stdout/stderr to job summary instead of truncating. This exposes all 11 lint errors detected in RUN pingdotgg#13.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Sign up for freeto 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

@juliusmarminge