Skip to content

Commit 65c2a41

Browse files
anandgupta42claude
andcommitted
merge: bring origin/main into upstream/merge-v1.17.9
Merge the 5 commits on `main` since this branch was cut (bearer-auth remote MCP #793, session_start `source` telemetry #968, aireceipts CI #992, REVIEW.md docs #983/#984) into the upstream OpenCode v1.17.9 bridge branch. 9 conflicts resolved: - `altimate/telemetry/index.ts` — keep upstream `InstallationVersion`; re-add #968's `Flag`/`source` (per-session client label). - `session/index.ts` — keep upstream `permission` readonly cast AND #968's `metadata: row.metadata`. Session `metadata` already exists upstream (core `SessionTable`, upstream PR #23068), so #968's persistence layer is redundant. - `session/session.sql.ts` — accept upstream deletion (persistence moved to `@opencode-ai/core`); dropped #968's duplicate migration `20260626041744_elite_malcolm_colcord` (would crash "duplicate column: metadata" against upstream's `20260511173437_session-metadata`). - `test/session/session.test.ts` — keep upstream Effect harness (main's metadata cases were the redundant old-harness variant). - `config/config.ts` — take upstream (schema relocated to core Effect Schema); re-apply #793's `headersCommand`/`oauth` normalize passthrough. - `mcp/index.ts` — re-port #793 (bearer-auth) onto upstream's Effect-based MCP rewrite: `resolveHeadersCommand` (execFile, no shell), case-insensitive `mergeHeaders`, lenient `listTools` retry, OAuth auto-disable folded into upstream's `supportsOAuth`, `maskString` on failures. Adapted `Either`→`Result` (effect 4.0.0-beta.74). - `core/v1/config/mcp.ts` — add `headersCommand` to the Effect Schema `Remote` struct as a mutable non-empty string-array record (`Schema.mutable(...).check (Schema.isNonEmpty())`), so `DeepMutable`-wrapped consumers type-check. - `test/mcp/headers.test.ts`, `test/mcp/mcp-bearer-auth.test.ts` — re-expressed #793's schema/integration tests against the Effect Schema and `it.instance` harness (Zod `safeParse` → `Schema.decodeUnknownSync`). - SDK artifacts kept at the upstream-HEAD baseline (regenerated by CI/release). Verification: all 13 packages typecheck clean (`bun turbo typecheck`); MCP + session + telemetry suites 194/194; bearer-auth + headers 32/32. Zero conflict markers. Marker guard non-strict pass (CI mode for upstream-merge PRs). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2 parents d009fd9 + a7fb223 commit 65c2a41

13 files changed

Lines changed: 1187 additions & 13 deletions

File tree

.github/workflows/aireceipts.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
name: aireceipts
2+
on: [pull_request]
3+
permissions:
4+
contents: read
5+
pull-requests: write
6+
jobs:
7+
check:
8+
runs-on: ubuntu-latest
9+
steps:
10+
- run: npx -y aireceipts-cli@latest pr-check
11+
env:
12+
GH_TOKEN: ${{ github.token }}

REVIEW.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# REVIEW.md — altimate-code
2+
3+
TypeScript/Bun monorepo (Effect-based) — an `opencode` fork shipping the `altimate` CLI/TUI: deterministic SQL/dbt/lineage/FinOps tooling embeddable under any LLM agent, plus a GitHub Action for automated dbt PR review.
4+
5+
## Goal: catch what CI cannot
6+
7+
CI here enforces type-safety and marker presence, not runtime correctness of async/Effect flows, cross-platform behavior, or security-sensitive edges (permission checks, auth, path/HTML escaping). Focus review effort there.
8+
9+
## Don't duplicate CI — never flag
10+
11+
- **`bun turbo typecheck` / `tsc`** — pure type errors, unused type imports. If it compiles, don't relitigate types.
12+
- **Marker Guard** — presence/format of `altimate_change` marker comments is enforced by a dedicated CI job (`marker-guard` in `ci.yml`, see #872, #904), not at compile time. Don't flag a *missing* marker as a guess; CI will fail the PR. Do still flag *misuse* (see Focus Areas below) since Marker Guard checks presence, not correctness.
13+
- **`anti-slop.yml`** — advisory only (made non-blocking after #741 auto-closed legitimate PRs); don't treat its output as a merge gate.
14+
- **PR title/standards (`pr-standards.yml`)** — conventional-commit-style title checks are automated; don't nitpick title format.
15+
- Pre-existing issues in code the diff doesn't touch.
16+
- If a PR only touches `.claude/` skills, docs, or trivial config, skip deep review — see Comment style.
17+
18+
## Evidence standard — no speculation
19+
20+
Before flagging: trace the actual caller and the actual shipped surface. This repo carries a **parallel, unshipped "V2" runtime** (`packages/server` HttpApi, V2 projectors) alongside the shipped one (`packages/opencode/src/server`). Reviewers repeatedly had to correct findings that assumed V2 code was live. Always confirm which surface a change lands on before asserting impact.
21+
22+
## Severity calibration
23+
24+
- **Critical**: permission/auth bypass, unawaited Effect discarding a check, path/HTML injection, data loss on the shipped session/trace path, race conditions in shared session/worker state.
25+
- **Warning**: env/config resolution edge cases, cache invalidation gaps, retry/timeout values not justified by evidence, cross-platform assumptions (macOS-only paths, bash `set -u` crashes), test isolation leaks.
26+
- **Nit**: naming, comment clarity, minor duplication, non-shipped-surface cosmetics.
27+
28+
## Focus areas — bug classes this repo actually ships
29+
30+
1. **Effect values awaited without running them** (Critical). `ctx.ask(...)`-style helpers return an `Effect`, not a Promise; `await effectValue` awaits the *object*, never executes it — this shipped a real permission-check bypass (fixed via `await AppRuntime.runPromise(ctx.ask(...))`). **Flag when** an `Effect.promise`/`Effect.gen`-returning function is passed directly to `await`. **Verify** the function's return type first — Effect wrapping is easy to miss because it still type-checks.
31+
32+
2. **Race conditions in session/worker/tool lifecycle** (Critical, ~12 fix commits). Concurrent access to shared state (session cancel/idle flags, worker/server stop, file writes, dispatcher registries) without ordering guarantees: `cancel()` race + idle state on normal loop exit (#845), worker/server `stop()` moved into `finally` so an invalid `--session` no longer leaks them, stale-file race + error misclassification (#611), DuckDB concurrent-access retry. **Flag when** new code mutates session/worker/cache state from more than one async path without a lock or `finally`-guaranteed cleanup. **Verify** cleanup runs on both success and error/cancel paths.
33+
34+
3. **Env-var / config resolution correctness** (Warning→Critical, 8 fix commits). MCP config `${VAR}` interpolation needs full/recursive resolution, not single-pass; config-key normalization (`mcpServers``mcp`) and installer env injection have shipped broken more than once. **Flag when** a diff touches config loading or `${VAR}` interpolation. **Verify** resolution is applied everywhere the config is consumed, not just at one call site.
35+
36+
4. **Fork-merge hygiene: `altimate_change` marker misuse** (Warning). Every line diverging from upstream `opencode` must sit inside an `altimate_change start/end` marker (100% coverage is the current bar — 98 files/407 blocks). Marker Guard checks *presence*, not *correctness*. **Flag**: (a) redundant new markers nested inside an already-marked block (previously false-positived, #904); (b) upstream-only APIs (`LanguageModelV3*`, `specificationVersion: "v3"`) leaking into code that intentionally stays on `@ai-sdk/provider@2.0.1`/`LanguageModelV2*` — should be reverted/bridged, not silently upgraded.
37+
38+
5. **Auth/permission surface confusion (V2 vs shipped)** (Critical to flag correctly, Nit to over-flag). The shipped permission/OAuth routes live under `packages/opencode/src/server`; a structurally similar but **unshipped** V2 HttpApi exists under `packages/server`. **Flag when** a change weakens the shipped surface's check. **Verify first** which surface a route lives under — past "bugs" here were false positives against the dead V2 path.
39+
40+
6. **Telemetry/trace data loss across turns** (Warning). Trace waterfall, summary prompt, and chat tab have each independently lost data after an agent turn (trace corruption #865/#867, diff totals not persisted to session summary). **Flag when** new trace/telemetry-read state isn't confirmed to be re-published after summarize/snapshot/restart.
41+
42+
7. **dbt/warehouse connector correctness** (Warning, dbt=15 + snowflake/databricks/clickhouse — largest keyword cluster). dbt YAML file-kind classification, surfacing real dbt errors instead of generic "Could not parse", and warehouse-specific SQL (BigQuery `INFORMATION_SCHEMA`, Snowflake Cortex gating) have each shipped wrong. **Flag when** a diff edits a dbt file-kind regex or warehouse-specific SQL template. **Verify** file-kind matching handles `.py`/`.md` (not just `.sql`/`.yml`) and matches the most specific path segment, not the first match from root.
43+
44+
8. **SQL/HTML injection in generated strings** (Critical, rare but severe). Hand-built SQL/HTML instead of parameterized binds/escaping: parameterized binds replaced `escapeSqlString` in finops/schema (#277); XSS fixed in `oauth-callback.ts`/`plugin/codex.ts` via `escapeHtml()`; symlink escape in `plugin/shared.ts` fixed via `Filesystem.containsReal` (resolves symlinks) instead of `.contains`. **Flag** any new SQL/HTML string interpolation of external input, or path-containment checks that don't resolve symlinks first.
45+
46+
9. **Schema/config cache invalidation** (Warning). `Config.update()`/`updateGlobal()` needed to explicitly invalidate the per-directory `ScopedCache` (disposal alone didn't clear it); webfetch 404s were cached too long. **Flag** new cached derived values from config/fetch without an explicit invalidation path.
47+
48+
10. **Cross-platform shell/path assumptions** (Nit→Warning, dense in `.claude/skills/*` review comments). Scripts hardcode macOS paths (Homebrew, Chrome.app, `java_home`), assume Bash ≥4.4 array expansion under `set -u`, or skip forcing UTF-8 locale. **Flag** `.sh` edits using `set -u` with array expansions, single-OS hardcoded paths with no fallback, or legacy `java -version` parsing.
49+
50+
11. **Test isolation leaks under parallel CI** (Warning). `mock.module`/dispatcher `reset()` leaked state across test files (52 CI failures fixed via #460). **Flag** new tests using global mocks/dispatchers without confirmed teardown under parallel `bun test`.
51+
52+
## Repo invariants & landmines
53+
54+
- Effect (`effect` v4 beta) is used pervasively; `Effect.promise`/`Effect.sync`-returning functions look like normal async functions but must be run via `AppRuntime.runPromise` (or equivalent) — a bare `await` silently no-ops the effect.
55+
- Ships two SDK generations: `LanguageModelV2*` (`@ai-sdk/provider@2.0.1`, current) vs `V3` (upstream-only) — V3 types/fields must not appear in code on the shipped path.
56+
- `packages/opencode/src/server` is the shipped server; `packages/server` is a not-yet-shipped V2 HttpApi — don't assume the latter affects users.
57+
- The binary ships as `altimate` (npm exposes both `altimate` and `altimate-code` on PATH); dbt/dbt-tools and skills are bundled into the published binary, so unbundled-dependency regressions (e.g. `.node` binaries, `@altimateai/altimate-core`) break the *published* artifact even when `bun run` in dev works fine.
58+
- SQLite is the storage engine (`bun:sqlite`, migrated off `better-sqlite3`); schema is core-owned — a fresh DB may lack columns the fork's queries assume (`permission.data` was one such case).
59+
60+
## Known-intentional — don't flag
61+
62+
- `@ts-expect-error`/documented `as any` casts left in provider/TUI bridge code for known upstream SDK-shape mismatches during a bridge-merge PR (e.g. opentui `traits`, markdown `fg` prop) — these are intentionally temporary and explained inline.
63+
- Tying cleanup lifecycles to `server.instance.disposed` instead of an `Effect` scope — reviewers explicitly declined this refactor as changing instance-bootstrap semantics for no correctness gain.
64+
65+
## High-blast-radius — never auto-edit
66+
67+
`packages/opencode/src/server/**` core routing, `db.ts`/storage migration code, `install`/`install.ps1`, `.github/workflows/*.yml`, `patches/`, `bun.lock`, `packages/*/package.json` version pins, anything under `test/upstream/` (bridge-merge regression suite).
68+
69+
## Comment style
70+
71+
Exact-line comments, one finding each, most-severe first. Prefer a `suggestion` block over prose when the fix is a small diff. Skip deep review of doc-only, `.claude/` skill, and CHANGELOG-only diffs — a clean diff gets `lgtm`.

packages/core/src/v1/config/mcp.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,22 @@ export const Remote = Schema.Struct({
5050
headers: Schema.optional(Schema.Record(Schema.String, Schema.String)).annotate({
5151
description: "Headers to send with the request",
5252
}),
53+
// altimate_change start — dynamic header resolution for short-TTL bearer tokens
54+
// (e.g. `az account get-access-token`). Each value is an argv array re-run on
55+
// every connect; values here override matching `headers` keys (case-insensitive).
56+
// See https://github.com/AltimateAI/altimate-code/issues/791.
57+
headersCommand: Schema.optional(
58+
Schema.Record(Schema.String, Schema.mutable(Schema.Array(Schema.String)).check(Schema.isNonEmpty())),
59+
).annotate({
60+
description:
61+
"Commands to run to resolve dynamic header values (e.g. short-lived bearer tokens). Each value is an argv array run without a shell, re-resolved on every connect. Overrides matching keys in `headers`.",
62+
}),
63+
// altimate_change end
5364
oauth: Schema.optional(Schema.Union([OAuth, Schema.Literal(false)])).annotate({
54-
description: "OAuth authentication configuration for the MCP server. Set to false to disable OAuth auto-detection.",
65+
description:
66+
// altimate_change start — document the auto-disable behavior added for #792
67+
"OAuth authentication configuration for the MCP server. Set to false to disable OAuth auto-detection. OAuth is also automatically skipped when an explicit Authorization header is provided (via `headers` or `headersCommand`) and this field is not explicitly configured.",
68+
// altimate_change end
5569
}),
5670
timeout: Schema.optional(PositiveInt).annotate({
5771
description: "Timeout in ms for MCP server requests. Defaults to 5000 (5 seconds) if not specified.",

packages/opencode/src/altimate/telemetry/index.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Account } from "@/account"
22
import { Config } from "@/config/config"
3+
import { Flag } from "@/flag/flag"
34
import { InstallationVersion } from "@opencode-ai/core/installation/version"
45
import { Log } from "@/altimate/util/log"
56
import { createHash, randomUUID } from "crypto"
@@ -62,6 +63,7 @@ export namespace Telemetry {
6263
os: string
6364
arch: string
6465
node_version: string
66+
source?: string
6567
}
6668
// altimate_change end
6769
| {
@@ -1220,12 +1222,21 @@ export namespace Telemetry {
12201222
}
12211223

12221224
function toAppInsightsEnvelopes(events: Event[], cfg: AppInsightsConfig): object[] {
1225+
// Process-level client, read live from the env flag (defaults to "cli") so events
1226+
// emitted before any prompt runs — startup, connection setup, standalone CLI
1227+
// subcommands — are labelled correctly instead of stuck at a hardcoded default.
1228+
// Any event that already carries its own `source` field overrides this via the field
1229+
// loop below — session_start (the per-session client, from session.metadata.source) and
1230+
// a few pre-existing events (skill_*, connections) whose `source` means something else
1231+
// (cli/tui, connection origin). Events without their own `source` report this value.
1232+
const clientSource = Flag.ALTIMATE_CLI_CLIENT
12231233
return events.map((event) => {
12241234
const { type, timestamp, ...fields } = event as any
12251235
const sid: string = fields.session_id ?? sessionId
12261236

12271237
const properties: Record<string, string> = {
12281238
cli_version: InstallationVersion,
1239+
source: clientSource,
12291240
project_id: fields.project_id ?? projectId,
12301241
...(machineId && { machine_id: machineId }),
12311242
}

packages/opencode/src/config/config.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,20 @@ function normalizeMcpConfig(data: Record<string, unknown>, source: string): Reco
108108
servers[name] = transformed
109109
} else if (entry.url && typeof entry.url === "string") {
110110
const transformed: Record<string, any> = { type: "remote", url: entry.url }
111+
// Copy `headers` / `headersCommand` through as-is — including malformed
112+
// array shapes. The downstream schema validation rejects an array with an
113+
// actionable error; stripping arrays here would instead drop the field
114+
// silently and connect a header-less server with no feedback. See #791 / #792.
111115
if (entry.headers && typeof entry.headers === "object") transformed.headers = entry.headers
116+
// altimate_change start — preserve bearer-auth fields the original normalizer
117+
// dropped silently. Without these passes, a user-supplied `oauth: false` or
118+
// `headersCommand` would be reconstructed-away, leaving the runtime believing
119+
// the config was bare. See #791 / #792.
120+
if (entry.headersCommand && typeof entry.headersCommand === "object") {
121+
transformed.headersCommand = entry.headersCommand
122+
}
123+
if (entry.oauth !== undefined) transformed.oauth = entry.oauth
124+
// altimate_change end
112125
if (typeof entry.timeout === "number") transformed.timeout = entry.timeout
113126
if (typeof entry.enabled === "boolean") transformed.enabled = entry.enabled
114127
if (typeof entry.updatedAt === "string") transformed.updatedAt = entry.updatedAt

packages/opencode/src/mcp/discover.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,48 @@ function transform(
8989
})
9090
// altimate_change end
9191
}
92+
// altimate_change start — preserve bearer-auth fields so a discovered
93+
// server's `headersCommand` / `oauth` isn't dropped before reaching the
94+
// runtime, silently connecting with no auth. Unlike config.ts
95+
// `normalizeMcpConfig` (which passes malformed shapes through so the user's
96+
// own file fails `Info.safeParse` with an actionable error), discovery
97+
// ingests FOREIGN config files: only shapes our McpRemote schema accepts
98+
// are preserved, and foreign dialects (e.g. Gemini CLI's
99+
// `oauth: { enabled: true }`) are dropped as before. Discovered entries are
100+
// merged into the runtime config after validation and can be persisted to
101+
// opencode.json via `mcp-discover add`, so an unvalidated pass-through
102+
// would poison the config file and fail every subsequent load.
103+
// Validators mirror the McpRemote schema: `headersCommand` is a record of
104+
// non-empty string argv arrays; `oauth` is `false` or a strict object of
105+
// optional string fields clientId/clientSecret/scope. (Schemas can't be
106+
// imported as values here — config.ts dynamically imports this module, and
107+
// the Config import above is type-only to avoid a static cycle.)
108+
// See #791 / #792.
109+
const headersCommand = entry.headersCommand
110+
if (headersCommand !== undefined) {
111+
const valid =
112+
headersCommand !== null &&
113+
typeof headersCommand === "object" &&
114+
!Array.isArray(headersCommand) &&
115+
Object.values(headersCommand).every(
116+
(argv) => Array.isArray(argv) && argv.length > 0 && argv.every((part: unknown) => typeof part === "string"),
117+
)
118+
if (valid) result.headersCommand = headersCommand
119+
else log.debug("dropping unrecognized headersCommand from discovered server", context)
120+
}
121+
const oauth = entry.oauth
122+
if (oauth !== undefined) {
123+
const oauthStringFields = ["clientId", "clientSecret", "scope"]
124+
const valid =
125+
oauth === false ||
126+
(oauth !== null &&
127+
typeof oauth === "object" &&
128+
!Array.isArray(oauth) &&
129+
Object.entries(oauth).every(([k, v]) => oauthStringFields.includes(k) && typeof v === "string"))
130+
if (valid) result.oauth = oauth
131+
else log.debug("dropping unrecognized oauth config from discovered server", context)
132+
}
133+
// altimate_change end
92134
if (typeof entry.timeout === "number") result.timeout = entry.timeout
93135
if (typeof entry.enabled === "boolean") result.enabled = entry.enabled
94136
return result as ConfigMCPV1.Info

0 commit comments

Comments
 (0)