From b301805e6071814a29b45893379bcfd92bc5df36 Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Sun, 14 Jun 2026 17:31:33 +0800 Subject: [PATCH 1/4] fix: [#939] trigger auto-update check on headless `serve` startup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Auto-update was wired solely into the TUI bootstrap (`cli/cmd/tui/thread.ts` → `worker.checkUpgrade` → `upgrade()`). The headless `serve` command — how the VS Code / Cursor extension runs altimate-code — never checked for or installed updates, so the extension fleet froze at whatever version was installed at onboarding (e.g. `0.7.3`), regardless of the `autoupdate` setting. Mirror the TUI in `serve`: fire a single best-effort `upgrade()` check shortly after the server is listening, via the existing `bootstrap()` helper. All policy stays inside `upgrade()` — install-method detection, the `autoupdate` gate (`true` / `false` / `"notify"`), the downgrade guard, and telemetry — so this only adds the missing trigger. Fire-and-forget, detached from request handling, errors logged not thrown. Closes #939 --- packages/opencode/src/cli/cmd/serve.ts | 27 ++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/packages/opencode/src/cli/cmd/serve.ts b/packages/opencode/src/cli/cmd/serve.ts index 528498801..1650e17d9 100644 --- a/packages/opencode/src/cli/cmd/serve.ts +++ b/packages/opencode/src/cli/cmd/serve.ts @@ -8,6 +8,13 @@ import { Installation } from "../../installation" // altimate_change start — trace: session tracing in headless serve import { subscribeTraceConsumer } from "../../altimate/observability/trace-consumer" // altimate_change end +// altimate_change start — self-update on headless serve startup +import { bootstrap } from "../bootstrap" +import { upgrade } from "../upgrade" +import { Log } from "../../util/log" + +const log = Log.create({ service: "serve" }) +// altimate_change end export const ServeCommand = cmd({ command: "serve", @@ -35,6 +42,26 @@ export const ServeCommand = cmd({ // (`tracing.dir`, default ~/.local/share/altimate-code/traces/). const traceSub = subscribeTraceConsumer({ directory: process.cwd() }) + // altimate_change start — self-update on startup + // A headless `serve` is how the VS Code / Cursor extension runs + // altimate-code, and it is the ONLY long-running entrypoint that never + // checked for updates: auto-update was wired solely into the TUI bootstrap + // (cli/cmd/tui/thread.ts → worker.checkUpgrade → upgrade()). As a result the + // extension fleet froze at whatever version was installed at onboarding. + // + // Mirror the TUI here: fire a single best-effort upgrade check shortly after + // the server is listening. All policy lives inside upgrade() — install-method + // detection, the `autoupdate` config gate (true / false / "notify"), the + // downgrade guard, and telemetry — so this is just the missing trigger. + // Fire-and-forget and fully detached from request handling; errors are logged, + // never thrown, so a flaky network or registry can't take the server down. + setTimeout(() => { + bootstrap(process.cwd(), () => + upgrade().catch((err) => log.error("startup upgrade check failed", { error: String(err) })), + ).catch((err) => log.error("startup upgrade bootstrap failed", { error: String(err) })) + }, 1000).unref?.() + // altimate_change end + // Finalize traces on shutdown. `serve` blocks forever on the promise below // and otherwise dies abruptly on signal, so without these handlers the // consumer's stop()/flush()/endTrace() never runs and serve traces are From 49f64ff5f9c2fa7cbb9c1d2d91d4f98d93234d56 Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Mon, 15 Jun 2026 12:13:12 +0800 Subject: [PATCH 2/4] fix: [#939] isolate serve upgrade check from shared request instances MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address review on #940: the startup upgrade check wrapped `upgrade()` in `bootstrap()`, whose `finally` calls `Instance.dispose()`. Running in-process with the HTTP server, that disposed the entire `process.cwd()` instance bucket — the same bucket server requests default to when no `directory` is supplied (`server/server.ts:196`) — tearing down state shared with in-flight requests (use-after-dispose / needless churn). The TUI path it mirrors is safe only because it runs in a separate worker thread. Fix: mirror the TUI worker faithfully — use raw `Instance.provide` on `process.cwd()` and, exactly like the worker, never dispose. This reuses/seeds the shared cached instance (Bus notifications still reach default-directory SSE subscribers) and removes the teardown entirely. `upgrade()` reads only global config + `Installation`; the instance context exists solely for `Bus.publish`. Also: extract the trigger into `serve-upgrade-check.ts` (named `STARTUP_UPGRADE_DELAY_MS`, `runStartupUpgradeCheck`, `scheduleStartupUpgradeCheck`) so it's unit-testable and self-documenting, and add tests asserting it runs `upgrade()` once in the `process.cwd()` instance and never rejects on failure. --- .../src/cli/cmd/serve-upgrade-check.ts | 49 +++++++++++++++ packages/opencode/src/cli/cmd/serve.ts | 21 ++----- .../test/cli/serve-upgrade-check.test.ts | 63 +++++++++++++++++++ 3 files changed, 116 insertions(+), 17 deletions(-) create mode 100644 packages/opencode/src/cli/cmd/serve-upgrade-check.ts create mode 100644 packages/opencode/test/cli/serve-upgrade-check.test.ts diff --git a/packages/opencode/src/cli/cmd/serve-upgrade-check.ts b/packages/opencode/src/cli/cmd/serve-upgrade-check.ts new file mode 100644 index 000000000..87a105996 --- /dev/null +++ b/packages/opencode/src/cli/cmd/serve-upgrade-check.ts @@ -0,0 +1,49 @@ +// altimate_change start — self-update trigger for headless serve +import { Instance } from "../../project/instance" +import { InstanceBootstrap } from "../../project/bootstrap" +import { upgrade } from "../upgrade" +import { Log } from "../../util/log" + +const log = Log.create({ service: "serve" }) + +/** Delay before the one-shot startup check, letting the listener settle first. */ +export const STARTUP_UPGRADE_DELAY_MS = 1000 + +/** + * Runs a single best-effort upgrade check, mirroring the TUI worker + * (`cli/cmd/tui/worker.ts` → `checkUpgrade`): provide an `Instance` context for + * `upgrade()` but — exactly like the worker — do NOT dispose it. + * + * `upgrade()` reads only global config + `Installation`, but `Bus.publish` + * (its update notifications) needs an ambient instance. We use the same + * `process.cwd()` key the server's default-directory requests use + * (`server/server.ts:196`), so we reuse/seed that shared cached instance rather + * than a throwaway one, and Bus notifications still reach default-directory SSE + * subscribers. + * + * Crucially we never dispose: an earlier version wrapped this in `bootstrap()`, + * whose `finally` → `Instance.dispose()` tears down the entire `process.cwd()` + * bucket — including state created by concurrent server requests that defaulted + * to that directory (use-after-dispose / needless churn). The worker avoids + * this by running in a separate thread; in-process we avoid it by not disposing. + * + * Resolves, never rejects: `upgrade()` swallows its own errors via the inner + * catch; the outer guard only fires for an `Instance.provide` failure. + */ +export async function runStartupUpgradeCheck(): Promise { + try { + await Instance.provide({ + directory: process.cwd(), + init: InstanceBootstrap, + fn: () => upgrade().catch((err) => log.error("startup upgrade check failed", { error: String(err) })), + }) + } catch (err) { + log.error("startup upgrade instance failed", { error: String(err) }) + } +} + +/** Schedules {@link runStartupUpgradeCheck} after a short settle delay; non-blocking. */ +export function scheduleStartupUpgradeCheck(): void { + setTimeout(() => void runStartupUpgradeCheck(), STARTUP_UPGRADE_DELAY_MS).unref?.() +} +// altimate_change end diff --git a/packages/opencode/src/cli/cmd/serve.ts b/packages/opencode/src/cli/cmd/serve.ts index 1650e17d9..ee7526595 100644 --- a/packages/opencode/src/cli/cmd/serve.ts +++ b/packages/opencode/src/cli/cmd/serve.ts @@ -9,11 +9,7 @@ import { Installation } from "../../installation" import { subscribeTraceConsumer } from "../../altimate/observability/trace-consumer" // altimate_change end // altimate_change start — self-update on headless serve startup -import { bootstrap } from "../bootstrap" -import { upgrade } from "../upgrade" -import { Log } from "../../util/log" - -const log = Log.create({ service: "serve" }) +import { scheduleStartupUpgradeCheck } from "./serve-upgrade-check" // altimate_change end export const ServeCommand = cmd({ @@ -48,18 +44,9 @@ export const ServeCommand = cmd({ // checked for updates: auto-update was wired solely into the TUI bootstrap // (cli/cmd/tui/thread.ts → worker.checkUpgrade → upgrade()). As a result the // extension fleet froze at whatever version was installed at onboarding. - // - // Mirror the TUI here: fire a single best-effort upgrade check shortly after - // the server is listening. All policy lives inside upgrade() — install-method - // detection, the `autoupdate` config gate (true / false / "notify"), the - // downgrade guard, and telemetry — so this is just the missing trigger. - // Fire-and-forget and fully detached from request handling; errors are logged, - // never thrown, so a flaky network or registry can't take the server down. - setTimeout(() => { - bootstrap(process.cwd(), () => - upgrade().catch((err) => log.error("startup upgrade check failed", { error: String(err) })), - ).catch((err) => log.error("startup upgrade bootstrap failed", { error: String(err) })) - }, 1000).unref?.() + // Fire the missing trigger here; see serve-upgrade-check.ts for why it runs + // in (but never disposes) the process.cwd() instance. + scheduleStartupUpgradeCheck() // altimate_change end // Finalize traces on shutdown. `serve` blocks forever on the promise below diff --git a/packages/opencode/test/cli/serve-upgrade-check.test.ts b/packages/opencode/test/cli/serve-upgrade-check.test.ts new file mode 100644 index 000000000..4eb874f39 --- /dev/null +++ b/packages/opencode/test/cli/serve-upgrade-check.test.ts @@ -0,0 +1,63 @@ +import { beforeEach, describe, expect, mock, test } from "bun:test" +import { Log } from "../../src/util/log" + +Log.init({ print: false }) + +// State the mocks record so each test can assert orchestration. +let upgradeCalls = 0 +let upgradeShouldThrow = false +let provideDirectory: string | undefined +let provideCalls = 0 + +// Mirror the real Instance.provide contract just enough: capture the directory +// key and run the supplied fn (no real instance, no dispose). +mock.module("../../src/project/instance", () => ({ + Instance: { + provide: async (input: { directory: string; fn: () => Promise }) => { + provideCalls++ + provideDirectory = input.directory + return input.fn() + }, + }, +})) + +mock.module("../../src/project/bootstrap", () => ({ + InstanceBootstrap: async () => {}, +})) + +mock.module("../../src/cli/upgrade", () => ({ + upgrade: async () => { + upgradeCalls++ + if (upgradeShouldThrow) throw new Error("boom") + }, +})) + +const { runStartupUpgradeCheck, STARTUP_UPGRADE_DELAY_MS } = await import("../../src/cli/cmd/serve-upgrade-check") + +describe("serve-upgrade-check", () => { + beforeEach(() => { + upgradeCalls = 0 + upgradeShouldThrow = false + provideDirectory = undefined + provideCalls = 0 + }) + + test("runs upgrade() once inside the process.cwd() instance", async () => { + await runStartupUpgradeCheck() + expect(upgradeCalls).toBe(1) + expect(provideCalls).toBe(1) + expect(provideDirectory).toBe(process.cwd()) + }) + + test("resolves without throwing when upgrade() rejects", async () => { + upgradeShouldThrow = true + // Must not reject — a flaky network/registry can't take the server down. + await expect(runStartupUpgradeCheck()).resolves.toBeUndefined() + expect(upgradeCalls).toBe(1) + }) + + test("uses a short, sane settle delay", () => { + expect(STARTUP_UPGRADE_DELAY_MS).toBeGreaterThan(0) + expect(STARTUP_UPGRADE_DELAY_MS).toBeLessThanOrEqual(5000) + }) +}) From d0b7b6eba8512585852ca6c9e83a717a31d877ac Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Mon, 15 Jun 2026 12:44:35 +0800 Subject: [PATCH 3/4] fix: [#939] inject serve-upgrade-check collaborators instead of mock.module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI "TypeScript" job failed: `SyntaxError: Export named 'isValidVersion' not found in module cli/upgrade.ts`. Cause was the new test — bun's `mock.module` is process-global, so mocking `../upgrade` (and `../../project/instance`, `../../project/bootstrap`) replaced those modules for the entire test run, stripping `cli/upgrade`'s other exports (`compareVersions`, `isValidVersion`) that `upgrade-decision.test.ts` imports. It passed locally only because the file was run in isolation. Replace module-mocking with dependency injection: `runStartupUpgradeCheck` takes an optional `StartupUpgradeDeps` ({ provide, run }) defaulting to the real `Instance.provide`(no-dispose) + `upgrade`. The test injects fakes directly — no global module state touched. Added a case for `provide()` itself rejecting. Verified in the combined suite (previously-colliding files together, then full test/cli): 512 pass, 0 fail; typecheck clean. --- .../src/cli/cmd/serve-upgrade-check.ts | 65 ++++++++------ .../test/cli/serve-upgrade-check.test.ts | 85 ++++++++++--------- 2 files changed, 86 insertions(+), 64 deletions(-) diff --git a/packages/opencode/src/cli/cmd/serve-upgrade-check.ts b/packages/opencode/src/cli/cmd/serve-upgrade-check.ts index 87a105996..cf2e0c255 100644 --- a/packages/opencode/src/cli/cmd/serve-upgrade-check.ts +++ b/packages/opencode/src/cli/cmd/serve-upgrade-check.ts @@ -10,33 +10,50 @@ const log = Log.create({ service: "serve" }) export const STARTUP_UPGRADE_DELAY_MS = 1000 /** - * Runs a single best-effort upgrade check, mirroring the TUI worker - * (`cli/cmd/tui/worker.ts` → `checkUpgrade`): provide an `Instance` context for - * `upgrade()` but — exactly like the worker — do NOT dispose it. + * Collaborators for {@link runStartupUpgradeCheck}, injectable for tests. * - * `upgrade()` reads only global config + `Installation`, but `Bus.publish` - * (its update notifications) needs an ambient instance. We use the same - * `process.cwd()` key the server's default-directory requests use - * (`server/server.ts:196`), so we reuse/seed that shared cached instance rather - * than a throwaway one, and Bus notifications still reach default-directory SSE - * subscribers. - * - * Crucially we never dispose: an earlier version wrapped this in `bootstrap()`, - * whose `finally` → `Instance.dispose()` tears down the entire `process.cwd()` - * bucket — including state created by concurrent server requests that defaulted - * to that directory (use-after-dispose / needless churn). The worker avoids - * this by running in a separate thread; in-process we avoid it by not disposing. - * - * Resolves, never rejects: `upgrade()` swallows its own errors via the inner - * catch; the outer guard only fires for an `Instance.provide` failure. + * Injected rather than module-mocked on purpose: bun's `mock.module` is + * process-global, so mocking `../upgrade` / `../../project/instance` here would + * clobber those modules for every other test in the run (e.g. blow away + * `cli/upgrade`'s `compareVersions`/`isValidVersion` exports). + */ +export interface StartupUpgradeDeps { + /** + * Runs `fn` inside an ambient `Instance` for `directory` and, like the TUI + * worker, does NOT dispose it (see note on the default below). + */ + provide: (directory: string, fn: () => Promise) => Promise + /** The upgrade check itself. */ + run: () => Promise +} + +const defaultDeps: StartupUpgradeDeps = { + // Mirror the TUI worker (cli/cmd/tui/worker.ts → checkUpgrade): provide an + // Instance context for upgrade() — Bus.publish needs one — but never dispose. + // + // We use the same process.cwd() key the server's default-directory requests + // use (server/server.ts:196), so we reuse/seed that shared cached instance and + // Bus notifications still reach default-directory SSE subscribers. Crucially we + // do NOT dispose: an earlier version wrapped this in bootstrap(), whose + // finally → Instance.dispose() tears down the entire process.cwd() bucket — + // including state created by concurrent server requests that defaulted to that + // directory (use-after-dispose / needless churn). The worker avoids this by + // running in a separate thread; in-process we avoid it by not disposing. + provide: (directory, fn) => Instance.provide({ directory, init: InstanceBootstrap, fn }), + run: upgrade, +} + +/** + * Runs a single best-effort upgrade check. Resolves, never rejects: `run()` + * (upgrade) swallows its own errors via the inner catch; the outer guard only + * fires for an `Instance.provide` failure. A flaky network/registry therefore + * can't take the server down. */ -export async function runStartupUpgradeCheck(): Promise { +export async function runStartupUpgradeCheck(deps: StartupUpgradeDeps = defaultDeps): Promise { try { - await Instance.provide({ - directory: process.cwd(), - init: InstanceBootstrap, - fn: () => upgrade().catch((err) => log.error("startup upgrade check failed", { error: String(err) })), - }) + await deps.provide(process.cwd(), () => + deps.run().catch((err) => log.error("startup upgrade check failed", { error: String(err) })), + ) } catch (err) { log.error("startup upgrade instance failed", { error: String(err) }) } diff --git a/packages/opencode/test/cli/serve-upgrade-check.test.ts b/packages/opencode/test/cli/serve-upgrade-check.test.ts index 4eb874f39..ff9e2c2c3 100644 --- a/packages/opencode/test/cli/serve-upgrade-check.test.ts +++ b/packages/opencode/test/cli/serve-upgrade-check.test.ts @@ -1,59 +1,64 @@ -import { beforeEach, describe, expect, mock, test } from "bun:test" +import { beforeEach, describe, expect, test } from "bun:test" import { Log } from "../../src/util/log" +import { runStartupUpgradeCheck, STARTUP_UPGRADE_DELAY_MS, type StartupUpgradeDeps } from "../../src/cli/cmd/serve-upgrade-check" Log.init({ print: false }) -// State the mocks record so each test can assert orchestration. -let upgradeCalls = 0 -let upgradeShouldThrow = false -let provideDirectory: string | undefined -let provideCalls = 0 - -// Mirror the real Instance.provide contract just enough: capture the directory -// key and run the supplied fn (no real instance, no dispose). -mock.module("../../src/project/instance", () => ({ - Instance: { - provide: async (input: { directory: string; fn: () => Promise }) => { - provideCalls++ - provideDirectory = input.directory - return input.fn() - }, - }, -})) - -mock.module("../../src/project/bootstrap", () => ({ - InstanceBootstrap: async () => {}, -})) - -mock.module("../../src/cli/upgrade", () => ({ - upgrade: async () => { - upgradeCalls++ - if (upgradeShouldThrow) throw new Error("boom") - }, -})) - -const { runStartupUpgradeCheck, STARTUP_UPGRADE_DELAY_MS } = await import("../../src/cli/cmd/serve-upgrade-check") - +// No mock.module here: it is process-global in bun and would clobber +// ../upgrade / ../../project/instance for every other test in the run. The +// collaborators are injected instead. describe("serve-upgrade-check", () => { + let runCalls: number + let runShouldThrow: boolean + let provideCalls: number + let provideDirectory: string | undefined + + function makeDeps(): StartupUpgradeDeps { + return { + provide: async (directory, fn) => { + provideCalls++ + provideDirectory = directory + return fn() + }, + run: async () => { + runCalls++ + if (runShouldThrow) throw new Error("boom") + }, + } + } + beforeEach(() => { - upgradeCalls = 0 - upgradeShouldThrow = false - provideDirectory = undefined + runCalls = 0 + runShouldThrow = false provideCalls = 0 + provideDirectory = undefined }) test("runs upgrade() once inside the process.cwd() instance", async () => { - await runStartupUpgradeCheck() - expect(upgradeCalls).toBe(1) + await runStartupUpgradeCheck(makeDeps()) + expect(runCalls).toBe(1) expect(provideCalls).toBe(1) expect(provideDirectory).toBe(process.cwd()) }) test("resolves without throwing when upgrade() rejects", async () => { - upgradeShouldThrow = true + runShouldThrow = true // Must not reject — a flaky network/registry can't take the server down. - await expect(runStartupUpgradeCheck()).resolves.toBeUndefined() - expect(upgradeCalls).toBe(1) + await expect(runStartupUpgradeCheck(makeDeps())).resolves.toBeUndefined() + expect(runCalls).toBe(1) + }) + + test("resolves without throwing when provide() itself rejects", async () => { + const deps: StartupUpgradeDeps = { + provide: async () => { + throw new Error("instance boom") + }, + run: async () => { + runCalls++ + }, + } + await expect(runStartupUpgradeCheck(deps)).resolves.toBeUndefined() + expect(runCalls).toBe(0) }) test("uses a short, sane settle delay", () => { From e6c26a1ab6d47eb40b07238aa8ecaba793c0f773 Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Mon, 15 Jun 2026 12:52:19 +0800 Subject: [PATCH 4/4] =?UTF-8?q?fix:=20[#939]=20address=20review=20nits=20?= =?UTF-8?q?=E2=80=94=20dead=20imports,=20log=20error=20objects,=20comment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to inline review on #940: - serve.ts: drop unused `Installation`, `Workspace`, `Project` imports (pre-existing dead code; Copilot flagged Installation, the other two are the same issue) — avoids future lint failures. - serve-upgrade-check.ts: pass the raw `Error` to `log.error` instead of `String(err)`, so `Log.formatError` emits the message + `cause` chain rather than a flattened string. - serve-upgrade-check.ts: correct the doc comment — the never-rejects guarantee comes from the explicit inner `.catch` wrapper (and outer try/catch), not from `upgrade()` swallowing its own errors. --- .../opencode/src/cli/cmd/serve-upgrade-check.ts | 14 ++++++++------ packages/opencode/src/cli/cmd/serve.ts | 3 --- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/packages/opencode/src/cli/cmd/serve-upgrade-check.ts b/packages/opencode/src/cli/cmd/serve-upgrade-check.ts index cf2e0c255..32d3f4a4f 100644 --- a/packages/opencode/src/cli/cmd/serve-upgrade-check.ts +++ b/packages/opencode/src/cli/cmd/serve-upgrade-check.ts @@ -44,18 +44,20 @@ const defaultDeps: StartupUpgradeDeps = { } /** - * Runs a single best-effort upgrade check. Resolves, never rejects: `run()` - * (upgrade) swallows its own errors via the inner catch; the outer guard only - * fires for an `Instance.provide` failure. A flaky network/registry therefore - * can't take the server down. + * Runs a single best-effort upgrade check. Resolves, never rejects, via two + * layers: the inner `.catch` swallows any error from `run()` (`upgrade()` can + * throw, e.g. from `Config.global()` / `Installation.method()`), and the outer + * try/catch guards an `Instance.provide` (bootstrap) failure. A flaky + * network/registry therefore can't take the server down. Errors are passed as + * `Error` objects so `Log` formats the message + `cause` chain. */ export async function runStartupUpgradeCheck(deps: StartupUpgradeDeps = defaultDeps): Promise { try { await deps.provide(process.cwd(), () => - deps.run().catch((err) => log.error("startup upgrade check failed", { error: String(err) })), + deps.run().catch((err) => log.error("startup upgrade check failed", { error: err })), ) } catch (err) { - log.error("startup upgrade instance failed", { error: String(err) }) + log.error("startup upgrade instance failed", { error: err }) } } diff --git a/packages/opencode/src/cli/cmd/serve.ts b/packages/opencode/src/cli/cmd/serve.ts index ee7526595..05c46f156 100644 --- a/packages/opencode/src/cli/cmd/serve.ts +++ b/packages/opencode/src/cli/cmd/serve.ts @@ -2,9 +2,6 @@ import { Server } from "../../server/server" import { cmd } from "./cmd" import { withNetworkOptions, resolveNetworkOptions } from "../network" import { Flag } from "../../flag/flag" -import { Workspace } from "../../control-plane/workspace" -import { Project } from "../../project/project" -import { Installation } from "../../installation" // altimate_change start — trace: session tracing in headless serve import { subscribeTraceConsumer } from "../../altimate/observability/trace-consumer" // altimate_change end