Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
d3b3641
[codex] Structure persistence error correlation (#3439)
juliusmarminge Jun 20, 2026
8fb9c4b
Preserve asset access failure causes (#3342)
juliusmarminge Jun 20, 2026
68f8a6c
[codex] Preserve PR materialization failure chains (#3443)
juliusmarminge Jun 20, 2026
29d5ab3
[codex] Structure pull request link failures (#3445)
juliusmarminge Jun 20, 2026
08feafd
[codex] Structure GitLab CLI failures (#3458)
juliusmarminge Jun 21, 2026
f52a759
[codex] Structure Bitbucket API errors (#3457)
juliusmarminge Jun 21, 2026
6d981b7
[codex] Structure Azure DevOps CLI failures (#3460)
juliusmarminge Jun 21, 2026
0e0e01b
[codex] Structure GitHub CLI failures (#3456)
juliusmarminge Jun 21, 2026
9a5c027
[codex] Structure VCS process boundary errors (#3476)
juliusmarminge Jun 21, 2026
69ac9d3
[codex] Preserve VCS project config error causes (#3474)
juliusmarminge Jun 21, 2026
d2853c8
[codex] Structure OpenCode text generation failures (#3472)
juliusmarminge Jun 21, 2026
982fa17
[codex] Structure primary auth validation failures (#3471)
juliusmarminge Jun 21, 2026
6a7c1a5
[codex] Structure thread archive blocked error (#3451)
juliusmarminge Jun 21, 2026
42a1503
[codex] Structure theme synchronization failures (#3466)
juliusmarminge Jun 21, 2026
79b0d91
Adapt Phase 3C web hardening to fork runtime
sukar0972 Jun 26, 2026
84ca569
Fix Phase 3C provider error adaptations
sukar0972 Jun 26, 2026
f21ed32
Format Phase 3C changes
sukar0972 Jun 26, 2026
08a525d
Keep Phase 3C compatible with fork services
sukar0972 Jun 26, 2026
f4fb015
Retain fork VCS contracts for Phase 3C
sukar0972 Jun 26, 2026
aaea4e1
Type-safe Phase 3C provider context
sukar0972 Jun 26, 2026
1b36e3e
Defer incompatible provider diagnostics
sukar0972 Jun 26, 2026
10e6415
Fix Phase 3C review findings for user-facing errors
sukar0972 Jun 26, 2026
cfc7e93
Print local and LAN pairing URLs on web server startup
sukar0972 Jun 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions apps/server/src/cli/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ export const devUrlFlag = Flag.string("dev-url").pipe(
Flag.optional,
);
export const noBrowserFlag = Flag.boolean("no-browser").pipe(
Flag.withDescription("Disable automatic browser opening."),
Flag.withDescription(
"Disable automatic browser opening (defaults to true; pass --no-browser false to enable).",
),
Flag.optional,
);
export const bootstrapFdFlag = Flag.integer("bootstrap-fd").pipe(
Expand Down Expand Up @@ -310,7 +312,7 @@ export const resolveServerConfig = (
Option.fromUndefinedOr(env.noBrowser),
Option.fromUndefinedOr(bootstrap?.noBrowser),
),
() => mode === "desktop",
() => true,
);
const desktopBootstrapToken = bootstrap?.desktopBootstrapToken;
const autoBootstrapProjectFromCwd = Option.getOrElse(
Expand Down
2 changes: 1 addition & 1 deletion apps/server/src/cli/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const startCommand = Command.make("start", { ...sharedServerCommandFlags

export const serveCommand = Command.make("serve", { ...sharedServerCommandFlags }).pipe(
Command.withDescription(
"Run the more Code server without opening a browser and print headless pairing details.",
"Run the more Code server in headless mode and print local and LAN pairing URLs.",
),
Command.withHandler((flags) =>
runServerCommand(flags, {
Expand Down
49 changes: 36 additions & 13 deletions apps/server/src/git/GitManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import {
type ChangeRequestTerminology,
} from "@t3tools/shared/sourceControl";

import { GitManagerError } from "@t3tools/contracts";
import { GitManagerError, GitPullRequestMaterializationError } from "@t3tools/contracts";
import { TextGeneration } from "../textGeneration/TextGeneration.ts";
import { ProjectSetupScriptRunner } from "../project/Services/ProjectSetupScriptRunner.ts";
import { extractBranchNameFromRemoteRef } from "./remoteRefs.ts";
Expand Down Expand Up @@ -629,9 +629,12 @@ export const makeGitManager = Effect.fn("makeGitManager")(function* () {
) =>
configurePullRequestHeadUpstreamBase(cwd, pullRequest, localBranch).pipe(
Effect.catch((error) =>
Effect.logWarning(
`GitManager.configurePullRequestHeadUpstream: failed to configure upstream for ${localBranch} -> ${pullRequest.headBranch} in ${cwd}: ${error.message}`,
).pipe(Effect.asVoid),
Effect.logWarning("GitManager.configurePullRequestHeadUpstream failed", {
cwd,
localBranch,
headBranch: pullRequest.headBranch,
cause: error,
}).pipe(Effect.asVoid),
),
);

Expand Down Expand Up @@ -689,12 +692,30 @@ export const makeGitManager = Effect.fn("makeGitManager")(function* () {
localBranch = pullRequest.headBranch,
) =>
materializePullRequestHeadBranchBase(cwd, pullRequest, localBranch).pipe(
Effect.catch(() =>
gitCore.fetchPullRequestBranch({
cwd,
prNumber: pullRequest.number,
branch: localBranch,
}),
Effect.catch((primaryCause) =>
gitCore
.fetchPullRequestBranch({
cwd,
prNumber: pullRequest.number,
branch: localBranch,
})
.pipe(
Effect.mapError(
(fallbackCause) =>
new GitPullRequestMaterializationError({
cwd,
pullRequestNumber: pullRequest.number,
headRepository: resolveHeadRepositoryNameWithOwner(pullRequest),
headBranch: pullRequest.headBranch,
localBranch,
cause: new AggregateError(
[primaryCause, fallbackCause],
`Repository-head and pull-request-ref fetches both failed for pull request #${pullRequest.number}.`,
{ cause: primaryCause },
),
}),
),
),
),
);
const fileSystem = yield* FileSystem.FileSystem;
Expand Down Expand Up @@ -1411,9 +1432,11 @@ export const makeGitManager = Effect.fn("makeGitManager")(function* () {
})
.pipe(
Effect.catch((error) =>
Effect.logWarning(
`GitManager.preparePullRequestThread: failed to launch worktree setup script for thread ${input.threadId} in ${worktreePath}: ${error.message}`,
).pipe(Effect.asVoid),
Effect.logWarning("GitManager.preparePullRequestThread setup script failed", {
threadId: input.threadId,
worktreePath,
cause: error,
}).pipe(Effect.asVoid),
),
);
};
Expand Down
61 changes: 53 additions & 8 deletions apps/server/src/persistence/Errors.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,55 @@
import * as Schema from "effect/Schema";
import * as SchemaIssue from "effect/SchemaIssue";

function formatSchemaIssuePath(path: ReadonlyArray<PropertyKey>): string {
return path
.map((segment) => (typeof segment === "number" ? `[${segment}]` : String(segment)))
.join(".");
}

function summarizeSchemaIssue(issue: SchemaIssue.Issue): string {
switch (issue._tag) {
case "Filter":
case "Encoding":
return `${issue._tag}(${summarizeSchemaIssue(issue.issue)})`;
case "Pointer": {
const inner = summarizeSchemaIssue(issue.issue);
const path = formatSchemaIssuePath(issue.path);
return path.length > 0 ? `${path}:${inner}` : inner;
}
case "Composite":
case "AnyOf":
return `${issue._tag}(${issue.issues.map(summarizeSchemaIssue).join(",")})`;
default:
return issue._tag;
}
}

// ===============================
// Core Persistence Errors
// ===============================

export const PersistenceErrorCorrelation = Schema.Union([
Schema.Struct({ sessionId: Schema.String }),
Schema.Struct({ currentSessionId: Schema.String }),
Schema.Struct({ pairingLinkId: Schema.String }),
Schema.Struct({ threadId: Schema.String }),
]);
export type PersistenceErrorCorrelation = typeof PersistenceErrorCorrelation.Type;

export class PersistenceSqlError extends Schema.TaggedErrorClass<PersistenceSqlError>()(
"PersistenceSqlError",
{
operation: Schema.String,
detail: Schema.String,
detail: Schema.optional(Schema.String),
correlation: Schema.optional(PersistenceErrorCorrelation),
cause: Schema.optional(Schema.Defect()),
},
) {
override get message(): string {
return `SQL error in ${this.operation}: ${this.detail}`;
return this.detail === undefined
? `SQL error in ${this.operation}`
: `SQL error in ${this.operation}: ${this.detail}`;
}
}

Expand All @@ -23,9 +58,23 @@ export class PersistenceDecodeError extends Schema.TaggedErrorClass<PersistenceD
{
operation: Schema.String,
issue: Schema.String,
correlation: Schema.optional(PersistenceErrorCorrelation),
cause: Schema.optional(Schema.Defect()),
},
) {
static fromSchemaError(
operation: string,
cause: Schema.SchemaError,
correlation?: PersistenceErrorCorrelation,
): PersistenceDecodeError {
return new PersistenceDecodeError({
operation,
issue: summarizeSchemaIssue(cause.issue),
...(correlation === undefined ? {} : { correlation }),
cause,
});
}

override get message(): string {
return `Decode error in ${this.operation}: ${this.issue}`;
}
Expand All @@ -43,12 +92,8 @@ export function toPersistenceSqlError(operation: string) {
}

export function toPersistenceDecodeError(operation: string) {
return (error: Schema.SchemaError): PersistenceDecodeError =>
new PersistenceDecodeError({
operation,
issue: SchemaIssue.makeFormatterDefault()(error.issue),
cause: error,
});
return (cause: Schema.SchemaError): PersistenceDecodeError =>
PersistenceDecodeError.fromSchemaError(operation, cause);
}

export function toPersistenceDecodeCauseError(operation: string) {
Expand Down
89 changes: 81 additions & 8 deletions apps/server/src/persistence/Layers/AuthPairingLinks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ import * as SqlClient from "effect/unstable/sql/SqlClient";
import * as SqlSchema from "effect/unstable/sql/SqlSchema";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import * as Option from "effect/Option";
import * as Schema from "effect/Schema";

import {
toPersistenceDecodeError,
toPersistenceSqlError,
PersistenceDecodeError,
type PersistenceErrorCorrelation,
PersistenceSqlError,
type AuthPairingLinkRepositoryError,
} from "../Errors.ts";
import {
Expand All @@ -20,11 +22,35 @@ import {
RevokeAuthPairingLinkInput,
} from "../Services/AuthPairingLinks.ts";

function toPersistenceSqlOrDecodeError(sqlOperation: string, decodeOperation: string) {
const AuthPairingLinkRawDbRow = Schema.Struct({
id: Schema.String,
credential: Schema.Unknown,
method: Schema.Unknown,
scopes: Schema.Unknown,
subject: Schema.Unknown,
label: Schema.Unknown,
proofKeyThumbprint: Schema.Unknown,
createdAt: Schema.Unknown,
expiresAt: Schema.Unknown,
consumedAt: Schema.Unknown,
revokedAt: Schema.Unknown,
});

const decodeAuthPairingLinkDbRow = Schema.decodeUnknownEffect(AuthPairingLinkRecord);

function toPersistenceSqlOrDecodeError(
sqlOperation: string,
decodeOperation: string,
correlation?: PersistenceErrorCorrelation,
) {
return (cause: unknown): AuthPairingLinkRepositoryError =>
Schema.isSchemaError(cause)
? toPersistenceDecodeError(decodeOperation)(cause)
: toPersistenceSqlError(sqlOperation)(cause);
? PersistenceDecodeError.fromSchemaError(decodeOperation, cause, correlation)
: new PersistenceSqlError({
operation: sqlOperation,
...(correlation === undefined ? {} : { correlation }),
cause,
});
}

const makeAuthPairingLinkRepository = Effect.gen(function* () {
Expand Down Expand Up @@ -65,7 +91,7 @@ const makeAuthPairingLinkRepository = Effect.gen(function* () {

const consumeAvailablePairingLinkRow = SqlSchema.findOneOption({
Request: ConsumeAuthPairingLinkInput,
Result: AuthPairingLinkRecord,
Result: AuthPairingLinkRawDbRow,
execute: ({ credential, proofKeyThumbprint, consumedAt, now }) =>
sql`
UPDATE auth_pairing_links
Expand Down Expand Up @@ -95,7 +121,7 @@ const makeAuthPairingLinkRepository = Effect.gen(function* () {

const listActivePairingLinkRows = SqlSchema.findAll({
Request: ListActiveAuthPairingLinksInput,
Result: AuthPairingLinkRecord,
Result: AuthPairingLinkRawDbRow,
execute: ({ now }) =>
sql`
SELECT
Expand Down Expand Up @@ -134,7 +160,7 @@ const makeAuthPairingLinkRepository = Effect.gen(function* () {

const getPairingLinkRowByCredential = SqlSchema.findOneOption({
Request: GetAuthPairingLinkByCredentialInput,
Result: AuthPairingLinkRecord,
Result: AuthPairingLinkRawDbRow,
execute: ({ credential }) =>
sql`
SELECT
Expand All @@ -160,6 +186,7 @@ const makeAuthPairingLinkRepository = Effect.gen(function* () {
toPersistenceSqlOrDecodeError(
"AuthPairingLinkRepository.create:query",
"AuthPairingLinkRepository.create:encodeRequest",
{ pairingLinkId: input.id },
),
),
);
Expand All @@ -172,6 +199,22 @@ const makeAuthPairingLinkRepository = Effect.gen(function* () {
"AuthPairingLinkRepository.consumeAvailable:decodeRow",
),
),
Effect.flatMap((rowOption) =>
Option.match(rowOption, {
onNone: () => Effect.succeed(Option.none()),
onSome: (row) =>
decodeAuthPairingLinkDbRow(row).pipe(
Effect.mapError((cause) =>
PersistenceDecodeError.fromSchemaError(
"AuthPairingLinkRepository.consumeAvailable:decodeRow",
cause,
{ pairingLinkId: row.id },
),
),
Effect.map(Option.some),
),
}),
),
);

const listActive: AuthPairingLinkRepositoryShape["listActive"] = (input) =>
Expand All @@ -182,6 +225,19 @@ const makeAuthPairingLinkRepository = Effect.gen(function* () {
"AuthPairingLinkRepository.listActive:decodeRows",
),
),
Effect.flatMap((rows) =>
Effect.forEach(rows, (row) =>
decodeAuthPairingLinkDbRow(row).pipe(
Effect.mapError((cause) =>
PersistenceDecodeError.fromSchemaError(
"AuthPairingLinkRepository.listActive:decodeRows",
cause,
{ pairingLinkId: row.id },
),
),
),
),
),
);

const revoke: AuthPairingLinkRepositoryShape["revoke"] = (input) =>
Expand All @@ -190,6 +246,7 @@ const makeAuthPairingLinkRepository = Effect.gen(function* () {
toPersistenceSqlOrDecodeError(
"AuthPairingLinkRepository.revoke:query",
"AuthPairingLinkRepository.revoke:decodeRows",
{ pairingLinkId: input.id },
),
),
Effect.map((rows) => rows.length > 0),
Expand All @@ -203,6 +260,22 @@ const makeAuthPairingLinkRepository = Effect.gen(function* () {
"AuthPairingLinkRepository.getByCredential:decodeRow",
),
),
Effect.flatMap((rowOption) =>
Option.match(rowOption, {
onNone: () => Effect.succeed(Option.none()),
onSome: (row) =>
decodeAuthPairingLinkDbRow(row).pipe(
Effect.mapError((cause) =>
PersistenceDecodeError.fromSchemaError(
"AuthPairingLinkRepository.getByCredential:decodeRow",
cause,
{ pairingLinkId: row.id },
),
),
Effect.map(Option.some),
),
}),
),
);

return {
Expand Down
Loading
Loading