Skip to content

fix(canvas+templates): fetch runtime dropdown from /templates registry - #1526

Merged
molecule-ai[bot] merged 3 commits into
stagingfrom
fix/canvas-runtime-dropdown-hermes-gemini
Apr 22, 2026
Merged

molecule-ai[bot] merged 3 commits into
stagingfrom
fix/canvas-runtime-dropdown-hermes-gemini

Conversation

@HongmingWang-Rabbit

Copy link
Copy Markdown
Contributor

Summary

  • `GET /templates` now returns the `runtime` field from each template's `config.yaml` (was being parsed then dropped)
  • `ConfigTab` fetches `/templates` and builds the runtime dropdown dynamically. Adding a template to `manifest.json` now propagates automatically — no canvas PR required
  • Static fallback list preserved for offline/older-backend scenarios

The bug

A Hermes workspace had `runtime=hermes` in the DB row (top badge rendered "hermes") but the Config dropdown showed "LangGraph (default)" because "hermes" wasn't among the hardcoded options. HTML `` silently fell back to the first option, and saving would have clobbered the runtime back to empty. Test plan [ ] Rebuild workspace-server image, hit `GET /templates` → response includes `runtime` field per entry [ ] Load canvas ConfigTab on a Hermes workspace → dropdown shows "Hermes Agent" as selected [ ] Load canvas offline (e.g. tenant unreachable) → dropdown falls back to the static 8-option list [ ] Add a new template to manifest.json, redeploy → appears in dropdown without code change Fixes the user-visible issue in `hongmingwang.moleculesai.app` where the Hermes workspace's Config tab showed the wrong runtime.

Canvas hardcoded 6 runtime options, drifting from manifest.json which
already registers hermes + gemini-cli as first-class workspace templates.
A Hermes workspace had runtime=hermes in its DB row but Config showed
"LangGraph (default)" — the HTML select fell back to its first option
because "hermes" wasn't listed, and saving would clobber the runtime
back to empty.

Now:
- GET /templates returns the runtime field from each cloned template's
  config.yaml (previously dropped on the floor)
- ConfigTab fetches /templates on mount, dedupes non-empty runtimes, and
  renders them as <option>s. Falls back to the static list if the fetch
  fails (offline, older backend), so the control never renders empty.

Adding a template to manifest.json now flows through automatically — no
canvas PR required.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment thread workspace/lib/pre_stop.py
from datetime import datetime, timezone
from typing import TYPE_CHECKING, Any

from .snapshot_scrub import scrub_snapshot
import os
import tempfile

import pytest
Comment thread workspace/lib/pre_stop.py
try:
os.remove(target)
logger.debug("Snapshot deleted: %s", target)
except FileNotFoundError:
Extends the dropdown fix so Model and Required Env also flow from
the template registry instead of being free-form fields the user
has to remember.

Template config.yaml now declares:

  runtime_config:
    model: <default>
    models:
      - id: nous-hermes-3-70b
        name: Nous Hermes 3 70B (Nous Portal)
        required_env: [HERMES_API_KEY]
      - id: nousresearch/hermes-3-llama-3.1-70b
        name: Hermes 3 70B (via OpenRouter)
        required_env: [OPENROUTER_API_KEY]

Platform: GET /templates now returns runtime + model + models[] per
template (was previously dropping runtime + ignoring runtime_config).

Canvas:
- Runtime dropdown built from /templates (was hardcoded 6 options)
- Model input becomes a datalist combobox; free-form input still
  allowed since model names rotate faster than templates
- Required Env Vars default to the selected model's required_env,
  labelled "(suggested)" so the user knows it's template-driven
- Everything falls back to a static list when /templates is
  unreachable, so offline editing still works

Follow-up: add models[] to the other 7 template repos (claude-code,
crewai, autogen, deepagents, openclaw, gemini-cli, langgraph). This
PR updates the platform + canvas; the Hermes template config update
goes in a separate PR against its own repo.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@HongmingWang-Rabbit

Copy link
Copy Markdown
Contributor Author

Expanded scope (commit 82d7bf0)

Extended this PR beyond runtime dropdown to cover Model + Required Env too — per user ask "the model should fetch dynamically what this runtime supports" + "the Required Env Vars should be dynamic as well, based on selected model".

Schema now surfaced through `/templates`

```yaml
runtime_config:
model:
models:
- id:
name:
required_env: [<API keys / secrets for this model>]
```

Canvas ConfigTab

  • Runtime select → from `/templates` (mount-time fetch)
  • Model input → `` combobox seeded by selected runtime's `models[]`, still free-form
  • Required Env Vars → defaults to selected model's `required_env`, labelled "(suggested)"; user edits override

Paired

@HongmingWang-Rabbit

Copy link
Copy Markdown
Contributor Author

Self-review (five-axis)

Re-read the diff cold. Findings below. TL;DR: one Important UX bug in how the "(suggested)" Required Env Vars propagate on save, plus missing tests. No Critical issues.

Important: suggested required_env is cosmetic-only

The "Required Env Vars (suggested)" display is derived but never committed to state, which means saving doesn't write the suggested values to config.yaml. Trace:

```tsx
values={
config.runtime_config?.required_env?.length
? config.runtime_config.required_env
: (currentModelSpec?.required_env ?? [])
}
```

  • On mount: `config.runtime_config.required_env` = whatever was in yaml (often empty for new workspaces, or the old model's list)
  • User picks a different model → `currentModelSpec` changes → TagList re-renders showing new suggestion
  • User doesn't interact with the TagList (thinking "it's already right") → state's `required_env` is unchanged
  • User hits Save → `toYaml(config)` serializes the OLD (possibly empty) `required_env` → workspace runs with the wrong env check

Concrete scenario: User selects "MiniMax M2.7 (via OpenRouter)", UI shows `OPENROUTER_API_KEY` as suggested, saves, workspace fails because config still has `HERMES_API_KEY` as the required env.

Fix: when the model input's value becomes an exact match for a known modelSpec id, commit that model's required_env to state — IF the current required_env is empty OR matches the previously-selected modelSpec's required_env (i.e. was itself template-driven, not user-edited). Rough shape:

```tsx
onChange={(e) => {
const v = e.target.value;
const nextSpec = availableModels.find((m) => m.id === v);
setConfig((prev) => {
const prevRequired = prev.runtime_config?.required_env ?? [];
const prevSpec = availableModels.find((m) => m.id === (prev.runtime_config?.model || prev.model));
const wasTemplateDriven =
prevRequired.length === 0 ||
JSON.stringify(prevRequired) === JSON.stringify(prevSpec?.required_env ?? []);
const nextRequired = wasTemplateDriven && nextSpec?.required_env
? nextSpec.required_env
: prevRequired;
return {
...prev,
runtime_config: { ...prev.runtime_config, model: v, required_env: nextRequired },
};
});
}}
```

Important: no tests

  • Backend: `templates_test.go` has no assertion that the /templates response now carries `runtime` or `models[]`. A test fixturing a config.yaml with `runtime_config.models` and asserting shape would catch a future regression where someone drops the new fields from the raw struct.
  • Canvas: no test for ConfigTab's runtime-dropdown-populated-from-fetch or model-datalist behaviour.

Per the user's own rule "E2E tests must verify data flow", the minimal adequate coverage here is a backend unit test on List(). Canvas test is nice-to-have.

Optional: duplicate-id defence

`` in the datalist — if a template author accidentally ships two model entries with the same id, React emits a dup-key warning and `availableModels.find((m) => m.id === currentModelId)` silently returns only the first, hiding the second entry's env. Use `key={`${m.id}-${i}`}` and consider a dev-time warning if backend returns duplicate ids per template.

Optional: extract RuntimeAndModelSection

ConfigTab is ~370 lines and growing. The new runtime/model/env block is ~60 self-contained lines — reasonable extraction when someone next touches this file.

Nit: string-match special case

`if (!v || v === "langgraph") continue;` — skip logic keyed on a literal. A const `const DEFAULT_RUNTIME = "langgraph"` would make it more grep-friendly. Not blocking.

FYI: not browser-verified

Canvas is deployed from Vercel, so this branch can't be exercised in browser until merge + Vercel redeploy. Went as far as typecheck + Go build + cold diff re-read. Acknowledged risk: visual/rendering regressions in the datalist combobox that only show up in Safari/Firefox.


Verdict

Request changes — primarily to fix the required_env propagation bug. Everything else is optional/nit.

Review turned up that the \"Required Env Vars (suggested)\" display
was cosmetic-only — users picking a different model saw the new
env suggestion in the TagList, but the values never made it into
state, so Save serialized an empty (or stale) required_env and the
workspace ran with the wrong auth check.

Canvas fixes:
- Model input onChange now commits the matched modelSpec's required_env
  to state — but only when the prior required_env was empty or matched
  the previous modelSpec's list (i.e. user hadn't manually edited).
  User-typed envs always win.
- Dropped the display-only fallback in TagList values; shows only what's
  actually in state.
- New \"Template suggests X, Apply\" hint button covers the edge case
  where state and template differ (existing workspace whose required_env
  lags the template's current recommendation).
- datalist option key now includes index so template authors shipping
  duplicate model ids don't trigger a silent React key collision.
- Small arraysEqual helper.

Backend tests:
- TestTemplatesList_RuntimeAndModelsRegistry — asserts /templates
  response carries runtime + models[] with per-model required_env.
- TestTemplatesList_LegacyTopLevelModel — asserts older templates with
  top-level model: still surface correctly, with empty Models[].

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@HongmingWang-Rabbit

Copy link
Copy Markdown
Contributor Author

Review fixes applied (commit 6f06ebf)

Important #1: required_env propagation

  • Model `onChange` now commits matched spec's `required_env` to state — but only when prior value was empty or matched the previous spec's list (user edits always win)
  • Dropped the display-only fallback in the TagList; shows only what's in state, so Save serializes honestly
  • Added "Template suggests X — Apply" affordance for the edge case where state diverges from the template (e.g. pre-existing workspace where required_env lags the template's current recommendation)
  • Small `arraysEqual` helper to compare env lists

Important #2: backend tests

  • `TestTemplatesList_RuntimeAndModelsRegistry` — asserts response carries `runtime` + `models[]` with per-model `required_env`
  • `TestTemplatesList_LegacyTopLevelModel` — asserts older templates with top-level `model:` still round-trip correctly, Models[] empty
  • Both pass locally

Optional: duplicate-id defence

  • `` — template authors shipping two models with the same id no longer silently collide

Deferred (optional, not blocking)

  • Extract RuntimeAndModelSection component — file's growing but still navigable
  • Const for "langgraph" default runtime string — nit

Verification

  • `go test ./internal/handlers -run TestTemplatesList_Runtime` — PASS
  • `go test ./internal/handlers -run TestTemplatesList_Legacy` — PASS
  • `npx tsc --noEmit` — clean wrt ConfigTab (pre-existing unrelated test-file errors remain)
  • Browser verification still pending (Vercel redeploys on merge)

Re-review: verdict flipped from Request changes → ready for merge modulo normal CI.

@molecule-ai molecule-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM. Canary staging workflow is well-designed with alerting auto-close on recovery.

@molecule-ai molecule-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

PR #1526 — fix(canvas+templates): fetch runtime dropdown from /templates registry

Approve. Solid multi-part fix. A few observations to document for future work:

Main fix (runtime field surfacing): Correct. templateSummary now includes runtime, and canvas/ConfigTab dynamically builds the dropdown from GET /templates. The silent fallback to first-option (the original bug) is fully resolved.

SSRF hardening (ssrf.go): Good defence-in-depth. Blocking RFC-1918 + metadata + link-local at the URL-resolution layer protects the A2A proxy path. The net.LookupHost approach correctly handles DNS rebinding over time. Test coverage is comprehensive.

E2E pipeline additions (canary, full SaaS, canvas Playwright): Worth calling out: the 30-min canary catches regressions between nightly runs, the canvas Playwright suite exercises the actual runtime dropdown in-browser, and the e2e-canvas teardown safety-net is a good pattern. These complement the existing API-level tests.

Org import (org_import.go): The workspace tree creation logic is clean. Env var expansion in workspace_dir (the ${WORKSPACE_DIR} fix) is handled correctly before validation. Plugin pre-install, channel adapters, and schedule cron expressions all have appropriate error handling with graceful skips.

One minor note for follow-up: ConfigTab.tsx adds ~150 lines with a new data-fetching pattern (/templates → build dropdown). If the /templates endpoint ever returns a template with a runtime value not in the static fallback list, the dropdown will still render the full runtime value from the registry. That's the right behavior, just worth verifying once against a Hermes-registered template on staging.

Clean fix, approved.

molecule-ai Bot pushed a commit to Molecule-AI/molecule-ai-workspace-template-claude-code that referenced this pull request Apr 22, 2026
…-agent)

Claude Code supports two auth paths that use different env vars:
- OAuth (via `claude login`) → CLAUDE_CODE_OAUTH_TOKEN, tied to a
  Claude Code subscription
- Direct API key → ANTHROPIC_API_KEY, pay-as-you-go via the Anthropic
  Console

Previously the template only listed CLAUDE_CODE_OAUTH_TOKEN, hiding the
API-key path and forcing API-key users to override manually. Now
models[] exposes both as distinct dropdown entries — users pick the
one matching the credential they have; canvas auto-suggests the right
env var.

Model IDs differ intentionally:
- OAuth entries use CLI aliases (sonnet/opus/haiku — resolve to latest)
- API-key entries use explicit versioned ids (claude-sonnet-4-6, etc.)

claude CLI accepts either auth style transparently — OAuth wins when
both are set, which preserves existing workspace behaviour.

Paired with EnterOS-AI/enter-os-core#1526 (platform + canvas).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
molecule-ai Bot pushed a commit to Molecule-AI/molecule-ai-workspace-template-hermes that referenced this pull request Apr 22, 2026
…in-dev-agent)

* feat(config): add models[] registry with per-model required_env

Canvas now reads this list to populate the Model dropdown and suggest
the correct API-key env vars when a model is selected, so users don't
have to remember which key each provider wants.

Paired with EnterOS-AI/enter-os-core#1526 on the platform side — older
platforms just ignore the new `models:` key, so this change is
backward-compatible.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(config): expose MiniMax models in canvas dropdown

MiniMax was already registered as a provider in providers.py (line 169,
MINIMAX_API_KEY → api.minimax.io/v1) but users had no way to discover
it from canvas. Adding 4 entries so the Hermes template's Model
dropdown surfaces both direct (MiniMax-M2.7, MiniMax-M1) and
OpenRouter-proxied (minimax/minimax-m2.7, minimax/minimax-m1) paths.

M2.7 is MiniMax's flagship coding-tuned model (~197K ctx). M1 is the
open-weight 1M-context reasoning model, useful for long-context tasks
without leaving the OSS ecosystem.

Description updated to reflect the full provider matrix. No
providers.py changes — MiniMax was already wired.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs+feat: clarify MiniMax Token Plan compat, add M2.7-highspeed

MiniMax Token Plan keys (sk-cp-*) and pay-as-you-go keys (sk-api-*)
both go through the same MINIMAX_API_KEY env var and same
api.minimax.io/v1 endpoint — MiniMax's backend routes by key prefix.
No code changes needed; comment added so operators know either key
works.

MiniMax-M2.7-highspeed added as a 9th model entry. It's a
Token-Plan-exclusive variant (same model, same price, higher rate
limits — the one you pick if you pay for the subscription tier).
Labelled so users without a Token Plan know to pick regular M2.7.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@molecule-ai
molecule-ai Bot force-pushed the staging branch 2 times, most recently from fad4065 to 0506e0c Compare April 22, 2026 13:54
@molecule-ai
molecule-ai Bot merged commit 359dc61 into staging Apr 22, 2026
3 checks passed
molecule-ai Bot pushed a commit to Molecule-AI/molecule-ai-workspace-template-claude-code that referenced this pull request Apr 22, 2026
…-agent)

Claude Code supports two auth paths that use different env vars:
- OAuth (via `claude login`) → CLAUDE_CODE_OAUTH_TOKEN, tied to a
  Claude Code subscription
- Direct API key → ANTHROPIC_API_KEY, pay-as-you-go via the Anthropic
  Console

Previously the template only listed CLAUDE_CODE_OAUTH_TOKEN, hiding the
API-key path and forcing API-key users to override manually. Now
models[] exposes both as distinct dropdown entries — users pick the
one matching the credential they have; canvas auto-suggests the right
env var.

Model IDs differ intentionally:
- OAuth entries use CLI aliases (sonnet/opus/haiku — resolve to latest)
- API-key entries use explicit versioned ids (claude-sonnet-4-6, etc.)

claude CLI accepts either auth style transparently — OAuth wins when
both are set, which preserves existing workspace behaviour.

Paired with EnterOS-AI/enter-os-core#1526 (platform + canvas).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
molecule-ai Bot pushed a commit to Molecule-AI/molecule-ai-workspace-template-claude-code that referenced this pull request Apr 22, 2026
Claude Code supports two auth paths that use different env vars:
- OAuth (via `claude login`) → CLAUDE_CODE_OAUTH_TOKEN, tied to a
  Claude Code subscription
- Direct API key → ANTHROPIC_API_KEY, pay-as-you-go via the Anthropic
  Console

Previously the template only listed CLAUDE_CODE_OAUTH_TOKEN, hiding the
API-key path and forcing API-key users to override manually. Now
models[] exposes both as distinct dropdown entries — users pick the
one matching the credential they have; canvas auto-suggests the right
env var.

Model IDs differ intentionally:
- OAuth entries use CLI aliases (sonnet/opus/haiku — resolve to latest)
- API-key entries use explicit versioned ids (claude-sonnet-4-6, etc.)

claude CLI accepts either auth style transparently — OAuth wins when
both are set, which preserves existing workspace behaviour.

Paired with EnterOS-AI/enter-os-core#1526 (platform + canvas).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
HongmingWang-Rabbit added a commit that referenced this pull request Apr 22, 2026
…forward-port

fix(canvas): forward-port dynamic runtime dropdown (#1526) to main
molecule-ai Bot pushed a commit that referenced this pull request Apr 22, 2026
Two related workflow hygiene changes:

## (1) canary-verify: graceful-skip when canary secrets absent

Before: canary-verify hit `scripts/canary-smoke.sh` which exited
non-zero when CANARY_TENANT_URLS was empty. Every main publish
ran → canary-verify failed → red check on main CI signal (7/7 in
past 24h). Noise, no value.

After: smoke step detects the missing-secrets case, writes a
warning to the step summary, sets an output `smoke_ran=false`,
and exits 0. The workflow completes green without pretending to
have tested anything.

Gated downstream: `promote-to-latest` now requires BOTH
`needs.canary-smoke.result == success` AND
`needs.canary-smoke.outputs.smoke_ran == true`. A skip does NOT
auto-promote — manual `promote-latest.yml` remains the release
gate while Phase 2 canary is absent (see
molecule-controlplane/docs/canary-tenants.md for the fleet
stand-up plan + decision framework).

When the canary fleet is stood up and secrets populated: delete
the early-exit branch + the smoke_ran gate. The workflow goes back
to its original "smoke gates promotion" semantics.

## (2) auto-promote-staging.yml — draft

New workflow that fires after CI / E2E Staging Canvas / E2E API /
CodeQL complete on the staging branch, checks that ALL four are
green on the same SHA, and fast-forwards `main` to that SHA.

Shipped disabled: the promote step is gated behind repo variable
`AUTO_PROMOTE_ENABLED=true`. Until that's set, the workflow
dry-runs and logs what it would have done. Toggle via Settings →
Variables when staging CI has been reliably green for a few days.

Safety:
- workflow_run events only fire on push to staging (PRs into
  staging don't promote).
- Every required gate must be `completed/success` on the same
  head_sha. Pending / failed / skipped / cancelled → abort.
- `--ff-only` push. Refuses to advance main if it has diverged
  from staging history (someone landed a direct-to-main commit
  that's not on staging). Human resolves the fork.
- `workflow_dispatch` with `force=true` lets us test the flow
  end-to-end before flipping the variable on.

Motivation: molecule-core#1496 has been open with 1172 commits
divergence between staging and main. Today that trapped PR #1526
(dynamic canvas runtime dropdown) on staging while prod users
hit the hardcoded-dropdown bug. Auto-promote retires the bulk
staging→main PR pattern once the staging CI it depends on is
reliable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
molecule-ai Bot pushed a commit that referenced this pull request Apr 23, 2026
…1526)

PR #1526 shipped the /templates registry + canvas dynamic Runtime /
Model / Required-Env fields on 2026-04-22 — but merged into the
staging branch, not main. The staging→main promotion PR #1496 has
been open unmerged for a while with 1172 commits divergence, so
prod (which builds from main) still carries the old hardcoded
dropdown.

Symptom seen on hongmingwang.moleculesai.app today:

- New Hermes Agent workspace (template declares runtime: hermes) loads
  Config tab → Runtime dropdown shows "LangGraph (default)" because
  there's no <option value="hermes"> in the hardcoded list; it falls
  back to empty-value silently.
- Model field is a plain TextInput with static placeholder
  "e.g. anthropic:claude-sonnet-4-6" — should be a combobox populated
  from the selected runtime's models[].
- Required Env Vars is a TagList with static placeholder
  "e.g. CLAUDE_CODE_OAUTH_TOKEN" — should auto-populate from the
  selected model's required_env.
- Net effect: "Save & Deploy" sends empty model + empty env to the
  provisioner → workspace instant-fails.

This PR cherry-picks the exact three files from PR #1526 (#359dc61
on staging) forward to main, without pulling the other 1171
commits:

- canvas/src/components/tabs/ConfigTab.tsx
  - RuntimeOption interface + FALLBACK_RUNTIME_OPTIONS (hermes,
    gemini-cli included)
  - useEffect fetches /templates and populates runtimeOptions
    dynamically
  - dropdown renders from runtimeOptions (no hardcoded list)
  - Model becomes a combobox with datalist of available models
    per selected runtime
  - Required Env Vars auto-populates from the selected model's
    required_env on model change

- workspace-server/internal/handlers/templates.go
  - /templates endpoint returns [{id, name, runtime, models}] with
    per-template models registry (id, name, required_env)

- workspace-server/internal/handlers/templates_test.go
  - Tests for runtime+models parsing and legacy top-level model
    fallback

The canvas Runtime dropdown now resolves "hermes" correctly;
Model dropdown shows the models[] from the hermes template; Env
auto-populates with HERMES_API_KEY (or whichever model selected).

Verified locally:
  - workspace-server builds clean
  - Template handler tests pass: TestTemplatesList_RuntimeAndModelsRegistry,
    TestTemplatesList_LegacyTopLevelModel, TestTemplatesList_NonexistentDir

Follow-up: the staging→main promotion gap (#1496) is the
underlying process issue. Either merge that PR or adopt a policy
of landing fixes directly on main (as several PRs have today).
Files here were chosen minimally to avoid pulling unrelated staging
changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
molecule-ai Bot pushed a commit that referenced this pull request Apr 23, 2026
Two related workflow hygiene changes:

## (1) canary-verify: graceful-skip when canary secrets absent

Before: canary-verify hit `scripts/canary-smoke.sh` which exited
non-zero when CANARY_TENANT_URLS was empty. Every main publish
ran → canary-verify failed → red check on main CI signal (7/7 in
past 24h). Noise, no value.

After: smoke step detects the missing-secrets case, writes a
warning to the step summary, sets an output `smoke_ran=false`,
and exits 0. The workflow completes green without pretending to
have tested anything.

Gated downstream: `promote-to-latest` now requires BOTH
`needs.canary-smoke.result == success` AND
`needs.canary-smoke.outputs.smoke_ran == true`. A skip does NOT
auto-promote — manual `promote-latest.yml` remains the release
gate while Phase 2 canary is absent (see
molecule-controlplane/docs/canary-tenants.md for the fleet
stand-up plan + decision framework).

When the canary fleet is stood up and secrets populated: delete
the early-exit branch + the smoke_ran gate. The workflow goes back
to its original "smoke gates promotion" semantics.

## (2) auto-promote-staging.yml — draft

New workflow that fires after CI / E2E Staging Canvas / E2E API /
CodeQL complete on the staging branch, checks that ALL four are
green on the same SHA, and fast-forwards `main` to that SHA.

Shipped disabled: the promote step is gated behind repo variable
`AUTO_PROMOTE_ENABLED=true`. Until that's set, the workflow
dry-runs and logs what it would have done. Toggle via Settings →
Variables when staging CI has been reliably green for a few days.

Safety:
- workflow_run events only fire on push to staging (PRs into
  staging don't promote).
- Every required gate must be `completed/success` on the same
  head_sha. Pending / failed / skipped / cancelled → abort.
- `--ff-only` push. Refuses to advance main if it has diverged
  from staging history (someone landed a direct-to-main commit
  that's not on staging). Human resolves the fork.
- `workflow_dispatch` with `force=true` lets us test the flow
  end-to-end before flipping the variable on.

Motivation: molecule-core#1496 has been open with 1172 commits
divergence between staging and main. Today that trapped PR #1526
(dynamic canvas runtime dropdown) on staging while prod users
hit the hardcoded-dropdown bug. Auto-promote retires the bulk
staging→main PR pattern once the staging CI it depends on is
reliable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
molecule-ai Bot pushed a commit that referenced this pull request Apr 23, 2026
…1526)

PR #1526 shipped the /templates registry + canvas dynamic Runtime /
Model / Required-Env fields on 2026-04-22 — but merged into the
staging branch, not main. The staging→main promotion PR #1496 has
been open unmerged for a while with 1172 commits divergence, so
prod (which builds from main) still carries the old hardcoded
dropdown.

Symptom seen on hongmingwang.moleculesai.app today:

- New Hermes Agent workspace (template declares runtime: hermes) loads
  Config tab → Runtime dropdown shows "LangGraph (default)" because
  there's no <option value="hermes"> in the hardcoded list; it falls
  back to empty-value silently.
- Model field is a plain TextInput with static placeholder
  "e.g. anthropic:claude-sonnet-4-6" — should be a combobox populated
  from the selected runtime's models[].
- Required Env Vars is a TagList with static placeholder
  "e.g. CLAUDE_CODE_OAUTH_TOKEN" — should auto-populate from the
  selected model's required_env.
- Net effect: "Save & Deploy" sends empty model + empty env to the
  provisioner → workspace instant-fails.

This PR cherry-picks the exact three files from PR #1526 (#359dc61
on staging) forward to main, without pulling the other 1171
commits:

- canvas/src/components/tabs/ConfigTab.tsx
  - RuntimeOption interface + FALLBACK_RUNTIME_OPTIONS (hermes,
    gemini-cli included)
  - useEffect fetches /templates and populates runtimeOptions
    dynamically
  - dropdown renders from runtimeOptions (no hardcoded list)
  - Model becomes a combobox with datalist of available models
    per selected runtime
  - Required Env Vars auto-populates from the selected model's
    required_env on model change

- workspace-server/internal/handlers/templates.go
  - /templates endpoint returns [{id, name, runtime, models}] with
    per-template models registry (id, name, required_env)

- workspace-server/internal/handlers/templates_test.go
  - Tests for runtime+models parsing and legacy top-level model
    fallback

The canvas Runtime dropdown now resolves "hermes" correctly;
Model dropdown shows the models[] from the hermes template; Env
auto-populates with HERMES_API_KEY (or whichever model selected).

Verified locally:
  - workspace-server builds clean
  - Template handler tests pass: TestTemplatesList_RuntimeAndModelsRegistry,
    TestTemplatesList_LegacyTopLevelModel, TestTemplatesList_NonexistentDir

Follow-up: the staging→main promotion gap (#1496) is the
underlying process issue. Either merge that PR or adopt a policy
of landing fixes directly on main (as several PRs have today).
Files here were chosen minimally to avoid pulling unrelated staging
changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
molecule-ai Bot pushed a commit that referenced this pull request Apr 23, 2026
Two related workflow hygiene changes:

## (1) canary-verify: graceful-skip when canary secrets absent

Before: canary-verify hit `scripts/canary-smoke.sh` which exited
non-zero when CANARY_TENANT_URLS was empty. Every main publish
ran → canary-verify failed → red check on main CI signal (7/7 in
past 24h). Noise, no value.

After: smoke step detects the missing-secrets case, writes a
warning to the step summary, sets an output `smoke_ran=false`,
and exits 0. The workflow completes green without pretending to
have tested anything.

Gated downstream: `promote-to-latest` now requires BOTH
`needs.canary-smoke.result == success` AND
`needs.canary-smoke.outputs.smoke_ran == true`. A skip does NOT
auto-promote — manual `promote-latest.yml` remains the release
gate while Phase 2 canary is absent (see
molecule-controlplane/docs/canary-tenants.md for the fleet
stand-up plan + decision framework).

When the canary fleet is stood up and secrets populated: delete
the early-exit branch + the smoke_ran gate. The workflow goes back
to its original "smoke gates promotion" semantics.

## (2) auto-promote-staging.yml — draft

New workflow that fires after CI / E2E Staging Canvas / E2E API /
CodeQL complete on the staging branch, checks that ALL four are
green on the same SHA, and fast-forwards `main` to that SHA.

Shipped disabled: the promote step is gated behind repo variable
`AUTO_PROMOTE_ENABLED=true`. Until that's set, the workflow
dry-runs and logs what it would have done. Toggle via Settings →
Variables when staging CI has been reliably green for a few days.

Safety:
- workflow_run events only fire on push to staging (PRs into
  staging don't promote).
- Every required gate must be `completed/success` on the same
  head_sha. Pending / failed / skipped / cancelled → abort.
- `--ff-only` push. Refuses to advance main if it has diverged
  from staging history (someone landed a direct-to-main commit
  that's not on staging). Human resolves the fork.
- `workflow_dispatch` with `force=true` lets us test the flow
  end-to-end before flipping the variable on.

Motivation: molecule-core#1496 has been open with 1172 commits
divergence between staging and main. Today that trapped PR #1526
(dynamic canvas runtime dropdown) on staging while prod users
hit the hardcoded-dropdown bug. Auto-promote retires the bulk
staging→main PR pattern once the staging CI it depends on is
reliable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
molecule-ai Bot pushed a commit that referenced this pull request Apr 23, 2026
…1526)

PR #1526 shipped the /templates registry + canvas dynamic Runtime /
Model / Required-Env fields on 2026-04-22 — but merged into the
staging branch, not main. The staging→main promotion PR #1496 has
been open unmerged for a while with 1172 commits divergence, so
prod (which builds from main) still carries the old hardcoded
dropdown.

Symptom seen on hongmingwang.moleculesai.app today:

- New Hermes Agent workspace (template declares runtime: hermes) loads
  Config tab → Runtime dropdown shows "LangGraph (default)" because
  there's no <option value="hermes"> in the hardcoded list; it falls
  back to empty-value silently.
- Model field is a plain TextInput with static placeholder
  "e.g. anthropic:claude-sonnet-4-6" — should be a combobox populated
  from the selected runtime's models[].
- Required Env Vars is a TagList with static placeholder
  "e.g. CLAUDE_CODE_OAUTH_TOKEN" — should auto-populate from the
  selected model's required_env.
- Net effect: "Save & Deploy" sends empty model + empty env to the
  provisioner → workspace instant-fails.

This PR cherry-picks the exact three files from PR #1526 (#359dc61
on staging) forward to main, without pulling the other 1171
commits:

- canvas/src/components/tabs/ConfigTab.tsx
  - RuntimeOption interface + FALLBACK_RUNTIME_OPTIONS (hermes,
    gemini-cli included)
  - useEffect fetches /templates and populates runtimeOptions
    dynamically
  - dropdown renders from runtimeOptions (no hardcoded list)
  - Model becomes a combobox with datalist of available models
    per selected runtime
  - Required Env Vars auto-populates from the selected model's
    required_env on model change

- workspace-server/internal/handlers/templates.go
  - /templates endpoint returns [{id, name, runtime, models}] with
    per-template models registry (id, name, required_env)

- workspace-server/internal/handlers/templates_test.go
  - Tests for runtime+models parsing and legacy top-level model
    fallback

The canvas Runtime dropdown now resolves "hermes" correctly;
Model dropdown shows the models[] from the hermes template; Env
auto-populates with HERMES_API_KEY (or whichever model selected).

Verified locally:
  - workspace-server builds clean
  - Template handler tests pass: TestTemplatesList_RuntimeAndModelsRegistry,
    TestTemplatesList_LegacyTopLevelModel, TestTemplatesList_NonexistentDir

Follow-up: the staging→main promotion gap (#1496) is the
underlying process issue. Either merge that PR or adopt a policy
of landing fixes directly on main (as several PRs have today).
Files here were chosen minimally to avoid pulling unrelated staging
changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
molecule-ai Bot pushed a commit that referenced this pull request Apr 23, 2026
Two related workflow hygiene changes:

## (1) canary-verify: graceful-skip when canary secrets absent

Before: canary-verify hit `scripts/canary-smoke.sh` which exited
non-zero when CANARY_TENANT_URLS was empty. Every main publish
ran → canary-verify failed → red check on main CI signal (7/7 in
past 24h). Noise, no value.

After: smoke step detects the missing-secrets case, writes a
warning to the step summary, sets an output `smoke_ran=false`,
and exits 0. The workflow completes green without pretending to
have tested anything.

Gated downstream: `promote-to-latest` now requires BOTH
`needs.canary-smoke.result == success` AND
`needs.canary-smoke.outputs.smoke_ran == true`. A skip does NOT
auto-promote — manual `promote-latest.yml` remains the release
gate while Phase 2 canary is absent (see
molecule-controlplane/docs/canary-tenants.md for the fleet
stand-up plan + decision framework).

When the canary fleet is stood up and secrets populated: delete
the early-exit branch + the smoke_ran gate. The workflow goes back
to its original "smoke gates promotion" semantics.

## (2) auto-promote-staging.yml — draft

New workflow that fires after CI / E2E Staging Canvas / E2E API /
CodeQL complete on the staging branch, checks that ALL four are
green on the same SHA, and fast-forwards `main` to that SHA.

Shipped disabled: the promote step is gated behind repo variable
`AUTO_PROMOTE_ENABLED=true`. Until that's set, the workflow
dry-runs and logs what it would have done. Toggle via Settings →
Variables when staging CI has been reliably green for a few days.

Safety:
- workflow_run events only fire on push to staging (PRs into
  staging don't promote).
- Every required gate must be `completed/success` on the same
  head_sha. Pending / failed / skipped / cancelled → abort.
- `--ff-only` push. Refuses to advance main if it has diverged
  from staging history (someone landed a direct-to-main commit
  that's not on staging). Human resolves the fork.
- `workflow_dispatch` with `force=true` lets us test the flow
  end-to-end before flipping the variable on.

Motivation: molecule-core#1496 has been open with 1172 commits
divergence between staging and main. Today that trapped PR #1526
(dynamic canvas runtime dropdown) on staging while prod users
hit the hardcoded-dropdown bug. Auto-promote retires the bulk
staging→main PR pattern once the staging CI it depends on is
reliable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@molecule-ai
molecule-ai Bot deleted the fix/canvas-runtime-dropdown-hermes-gemini branch May 20, 2026 06:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant