* feat(docker): parameterize Dockerfile.integrated with ARG FLAVOR=minimal|full
Add FLAVOR build arg that gates the AI CLI install layer (claude-code,
gemini-cli, grok-cli, opencode) so one Dockerfile produces both the
minimal (< 350 MB) and full (< 600 MB) integrated images.
Use BuildKit cache mount for /root/.npm to speed up repeated builds.
Part of #1251 (P1 — publish integrated images).
* feat(docker): emit startup deprecation warning in legacy ccs-dashboard entrypoint
Prepend a [WARN] line to stderr on every container start so operators
running ghcr.io/kaitranntt/ccs-dashboard:latest are notified to migrate
to ghcr.io/kaitranntt/ccs:latest. Sunset window: 2 releases. See #1251.
* test(docker): add image-size.sh budget assertion + unit test suite
image-size.sh: asserts docker image inspect .Size against a byte budget.
Usage: image-size.sh <image:tag> <max-bytes>
Budgets: minimal=350 MB (367001600), full=600 MB (629145600)
image-size-logic.test.sh: 6 mock-docker unit tests covering pass/fail
boundaries, bad arg count, and non-integer input. All 6 pass locally.
Part of #1251 (P1).
* ci(docker): extend docker-release.yml to publish ccs:latest and ccs:full
- Add publish-integrated job with matrix flavor: [minimal, full]
- minimal -> ghcr.io/kaitranntt/ccs:<ver> + :latest (when promoted)
- full -> ghcr.io/kaitranntt/ccs:full-<ver> + :full (when promoted)
- Multi-arch: linux/amd64 + linux/arm64 via buildx
- Scoped GHA cache per flavor to avoid cross-contamination
- Add promote_to_latest workflow_dispatch input (default false)
- rc.1 soak: first publish only pushes immutable version tag
- Mutable :latest/:full promoted only on release event OR explicit opt-in
- Add smoke-test job (post-publish) for each flavor
- Pulls the just-published version tag
- Asserts image size via tests/docker/image-size.sh
- Boots container, waits for healthcheck, probes :3000 and :8317
- Keep publish-dashboard job unchanged (legacy 2-release sunset)
- Updated labels to note deprecation status
All jobs run on self-hosted cliproxy runners. Part of #1251 (P1).
* docs(docker): document new image tags, add ccs-dashboard deprecation notices
docker/README.md:
- Add "Choosing an image" table (ccs:latest / ccs:full / ccs-dashboard deprecated)
- Update Quick Start section to use ccs:latest as primary example
- Add legacy image note with sunset timeline
CHANGELOG.md:
- Add Unreleased > ### Deprecated entry for ccs-dashboard:latest
pointing to migration path and #1251
README.md:
- Add one-line deprecation banner near top routing users to ccs:latest
Part of #1251 (P1).
Squash merge PR #1240.\n\nValidated locally from the PR head because trusted-author GitHub CI was skipped for this fork PR:\n- bun run validate\n- bun run build:all\n- bun run test:all\n- CCS_E2E_SKIP_BUILD=1 bun run test:e2e
Closes#1218.
When the user invokes `ccs <profile> agents` (and other Claude subcommands
like `mcp`, `doctor`, `plugin`, ...), CCS was unconditionally injecting
session-only Claude flags (`--append-system-prompt`, `--disallowedTools`,
`--settings`, official-channels plugin specs) and forwarding the
`DISABLE_TELEMETRY=1` env var from profile settings. Each of those
either errored out the subcommand (`error: unknown option
'--append-system-prompt'`) or silently flipped Claude into
non-interactive list mode, so the new `claude agents` agent view never
opened under CCS.
Add a small `claude-subcommand-detector` that recognizes the documented
Claude subcommand set after skipping known value-taking flags, then have
the three steering-prompt injectors (websearch, image-analysis, browser),
the cliproxy and settings launchers, and the official-channels plan
short-circuit when a subcommand invocation is detected. Also strip
`DISABLE_TELEMETRY` from the spawned env only for subcommand
invocations — upstream Claude Code uses that var as a kill switch for
the subcommand TUIs, and the user's telemetry preference still applies
to every normal interactive session.
Verified end-to-end against `ccs glm agents` and `ccs ck agents`: the
agent view opens correctly, no `unknown option` error, and all 1938
existing tests pass.
Keep active CCS workflows on self-hosted runners, gate self-hosted PR execution to trusted authors, and scope privileged release credentials to the exact git operations that need them.
Two pre-existing test bugs surfaced under CCS CI on the new claw self-
hosted runners but stayed hidden locally:
1. tests/unit/commands/cliproxy-routing-subcommand.test.ts mocks the
routing-subcommand module with only 3 exports, but
src/commands/cliproxy/index.ts statically imports 6. Bun resolves
the static import graph against the mock and reports
SyntaxError: Export named 'handleRoutingAffinitySet' not found
for every test in the file. Add the missing 3 exports
(handleRoutingAffinityStatus, handleRoutingAffinityHelp,
handleRoutingAffinitySet) to the mock and document why the mock
must mirror every named export of the target module.
2. tests/unit/hooks/websearch-transformer.test.ts builds the
"disallowed" trace path from process.cwd() and from os.homedir().
Both fall under the os.tmpdir() safe-prefix in two real
environments: CI runners with cwd == /tmp/runner/work/... and Bun
test isolation that re-roots HOME under tmpdir. The hook treats
them as safe, writes the trace, and the assertion that the file
does NOT exist fails. Anchor disallowedTracePath under /etc/...
instead so it cannot satisfy the tmpdir, /var/log, or
<CCS_HOME>/.ccs/logs prefixes in any host environment.
Both fixes are independent of the OAuth callback traceability change
that this branch otherwise carries, but ship together so the PR
clears CI on the new runner stack.
This reverts the runs-on change for ai-review.yml. All CI/CD must
stay on home infra (claw, docker LXC, etc); GitHub-hosted runners
are forbidden. The qodo-ai/pr-agent docker action mount issue will
be addressed by either configuring the self-hosted runner image to
use path-identical bind mounts so nested-docker volume sources
resolve correctly, or by switching to native CLI invocation
(pip install pr-agent) instead of the docker action.
Tracked separately; not blocking PR #1209.
The qodo-ai/pr-agent Docker action requires the GitHub event payload
at /github/workflow/event.json inside the action container. On
self-hosted runners that themselves run inside a Docker container
(e.g. myoung34/github-runner), the host docker daemon resolves the
volume mount /tmp/runner/work/_temp/_github_workflow:/github/workflow
against the host filesystem, where the runner's /tmp path does not
exist. The action container starts with an empty /github/workflow
mount and fails with FileNotFoundError on event.json.
AI review jobs do not need self-hosted runner access (no bun cache,
no internal infra). Switch both dispatch-review and pr-agent jobs
to ubuntu-latest so the volume mount resolves on the same host where
the action expects it.
* fix(dispatcher): preserve anthropic auth env for settings profiles on non-proxy path
API profiles whose `ANTHROPIC_BASE_URL` is classified as `'anthropic'`
(anthropic.com, paths containing `/anthropic`, ollama.com) skip the
local OpenAI-compat proxy. The non-proxy launch path stripped
`ANTHROPIC_BASE_URL` / `ANTHROPIC_AUTH_TOKEN` / `ANTHROPIC_API_KEY`
from the subprocess env without re-injecting them, so Claude Code
launched with no routing/auth in `process.env` and failed with
`Not logged in - Please run /login`. The `--settings` env block does
not satisfy Claude Code's auth check.
Pre-existing for `anthropic.com` and `/anthropic` profiles. Newly
broken in v7.77.0 for Ollama Cloud profiles - PR #1175 reclassified
`ollama.com` from `generic-chat-completion-api` to `anthropic`,
moving it from the proxy path onto this broken non-proxy path.
Fix by extending `stripAnthropicRoutingEnv` with an optional
`preserveFrom` parameter. Routing keys present in `preserveFrom`
survive the strip (with values from `preserveFrom`). Settings-type
profiles pass their own `settings.env` as the preserve source so
routing they explicitly supplied is kept while routing leaked from
the parent shell or `global.env` is dropped.
Wired into both call sites:
- `headless-executor.ts` (the `-p` headless executor)
- `settings-flow.ts` (the interactive flow, which then calls
`execClaude` - whose own `stripAnthropicRoutingEnv` pass on the
merged env now also takes `envVars` as the preserve source so the
caller-supplied routing is not stripped a second time before spawn)
Native Anthropic / Bedrock / Vertex profiles are unaffected (they
don't put routing keys in `settings.env`). The OpenAI-compat proxy
path is unaffected because `buildOpenAICompatProxyEnv` overrides
`BASE_URL` / `AUTH_TOKEN` with localhost values and explicitly deletes
`API_KEY` after this strip.
Replaces the Apr 21 defensive double-strip test (which was the
mechanism causing this regression on the interactive path) with a
test asserting the new contract: caller-supplied routing in `envVars`
survives, parent-process routing is still stripped.
* test: harden anthropic settings env preservation coverage
* fix: preserve explicit blank anthropic routing env
---------
Co-authored-by: Tam Nhu Tran <kaitran.ntt@gmail.com>
Resolve the dispatcher and dashboard conflicts from origin/dev while preserving the configured local CLIProxy port behavior. Also harden lifecycle port fallback and wait for the actual stopped port during binary installs.
All call sites that spawn or probe CLIProxyApiPlus now read
cliproxy_server.local.port from config via resolveLifecyclePort()
instead of using the hardcoded CLIPROXY_DEFAULT_PORT constant.
- Move resolveLifecyclePort helper to src/cliproxy/config/port-manager.ts
- Fix 7 call sites: ccs.ts, config-command.ts, copilot-executor.ts,
lifecycle.ts, binary-manager.ts, cliproxy-stats-routes.ts,
cliproxy-local-proxy.ts
- Remove duplicate resolveLocalCliproxyPort helper
- Cache port resolution in /proxy-status handler to avoid repeated I/O
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Issue #1161. Sweeps 127 files to import from
src/config/config-loader-facade.ts instead of unified-config-loader or
utils/config-manager directly.
WRITE callers (32 files): replaced raw saveUnifiedConfig /
mutateUnifiedConfig / updateUnifiedConfig calls with the facade's
cache-coherent wrappers saveConfig / mutateConfig / updateConfig. This
fixes a latent stale-cache window where direct writes through the
underlying loader bypassed the facade's memoization.
READ callers (95 files): mechanical import-path migration only —
function names unchanged because the facade re-exports them. No
behavior change.
Also updated:
- tests/unit/utils/browser/browser-setup.test.ts (DI interface rename)
- src/management/checks/image-analysis-check.ts (dynamic import rename)
- src/web-server/health-service.ts (dynamic require rename)
- src/ccs.ts (path prefix fix from sweep script)
After sweep: zero raw write callers remain outside src/config/. Direct
imports of config-manager remain only for symbols not in the facade
(getConfigPath, getCcsDirSource, etc). Behavior unchanged; full suite
passes 1824/1824.
Out of scope: switching loadOrCreateUnifiedConfig() callers to
getCachedConfig() — needs per-callsite cache-safety analysis. Tracked
as follow-up.
Refs #1161
Verifies that a single requestId propagates across at least 3 lifecycle
stages emitted from different modules, and that ALS context survives across
async boundaries (setImmediate, microtask, promise.then). Registered in
test:slow bucket per validate:ci-parity gate.
Refs #1141, #1138
- base-config-loader.ts moved from src/cliproxy/ to src/cliproxy/config/,
so __dirname relative path needs one more .. to reach config/ dir
- Update mock.module paths for proxy-detector and routing-strategy
in non-colocated test files