Skip to content

Fix --version, the unbuildable service image, and a NUL leak in status - #31

Merged
amsultan2010 merged 5 commits into
mainfrom
crosscode-readiness-assessment
Aug 4, 2026
Merged

Fix --version, the unbuildable service image, and a NUL leak in status#31
amsultan2010 merged 5 commits into
mainfrom
crosscode-readiness-assessment

Conversation

@amsultan2010

Copy link
Copy Markdown
Owner

What this changes

Four defects found while verifying the npm-packaging, JSON-envelope, Supabase-onboarding and CI-test work already on main: crosscode --version and -V failed with USAGE_ERROR (CLI, MCP server, build script); apps/service/Dockerfile could not build at all because it copied apps/vscode-extension/package.json, deleted in the 2026-08-02 CLI-only pass (coordination service, CI); status --json leaked headReflog, an internal sentinel carrying a raw NUL byte, into the first payload every agent reads (daemon); and several docs asserted things the code contradicts (docs).

Why

The image break meant the documented deployment path had not worked since that merge and no job noticed, so CI now builds it and the manifest list is discovered by a find instead of hand-written — the same staleness cannot recur. Likewise --version is the first thing anyone runs after installing from npm, and it also fixes crosscode-mcp reporting a hand-written serverInfo.version that nothing kept in step with the manifest.

Testing

pnpm test goes 296 → 301 (5 new tests covering the version flag including the run -- passthrough case, the status projection, and the stderr-only deprecation). Verified end to end against a fresh npm pack + npm install outside the repo, and docker build verified clean and with --no-cache.

  • pnpm build passes
  • pnpm test passes — 29 files, 301 passed / 19 skipped
  • pnpm test:postgres passes — 6 files, 19 tests, none skipped

Security / trust-boundary impact

Net improvement, no weakening. The repo had no .dockerignore, so the new manifest stage would have sent .git and any .env into the build context; one is added that keeps credentials out of image layers. status() now projects an explicit field list rather than spreading internal state, so the next internal field is private by default. The CROSSCODE_DASHBOARD_URL deprecation notice goes to stderr only, preserving the guarantee that --json stdout is exactly one parseable line. No change to auth, RLS, checkpoint/materialization safety, or excluded paths; reset detection and GitState are untouched, so no spurious transition on upgrade.

Related issues

None.

amsultan2010 and others added 5 commits August 4, 2026 01:47
`crosscode --version` and `-V` both failed with USAGE_ERROR. The parse loop
already handled commander's `commander.version` code, but `.version()` was
never called, so that branch was unreachable -- on the one flag people try
first after installing from npm.

The version comes from the root manifest, substituted into the new
apps/daemon/src/version.ts at bundle time, because an installed bundle has no
package.json at a path it can rely on. Both shipped entrypoints read that one
constant, which also fixes crosscode-mcp announcing a hand-written "0.1.0" as
its MCP serverInfo version that nothing kept in step with the manifest. Run
from source through tsx it reports 0.0.0-source rather than claiming to be
whatever release it was last built from.

Answered before commander sees it so it honours --json like every other
command, and scoped to leading flags so a -V bound for a child process of
`crosscode run --` is not intercepted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The image copied apps/vscode-extension/package.json, a directory deleted in
the 2026-08-02 CLI-only pass. COPY of a missing path is a hard error, so the
documented deployment path had not built since that merge and no job noticed.

Removing that one line would fix today's break, but the hand-written manifest
list is what let it rot, so the manifests are now discovered by a find in a
throwaway stage: add or remove a workspace member and the image follows.
Install-layer caching still holds, because that layer keys off the manifests
alone rather than the whole tree.

That stage copies the repo, and there was no .dockerignore -- node_modules,
.git and any .env would have gone to the daemon and into a layer. Added one.

CI now builds the image, which is the only thing that keeps any of this honest.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`crosscode status --json` -- the first thing every agent reads -- carried
headReflog, which joins a commit hash to its reflog subject with a raw NUL.
It is an internal change-detection sentinel with no consumer outside the
daemon process, and it reached the wire because status() spread the whole
RepositoryState.

status() now names its public fields instead, so this is a contract rather
than an accident and the next field added to RepositoryState is private until
someone decides otherwise. The MCP get_workspace_state tool forwards the same
object and is fixed with it.

The sentinel is still computed and still drives same-HEAD reset detection;
only its publication changed. GitState is untouched, so an existing checkout
sees no spurious transition on upgrade.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The variable outlived the dashboard: it names a site that no longer has one.
It stays readable so setups made before the removal keep working, but now
warns once per process and defers to CROSSCODE_WEB_URL whenever both are set.

The notice goes to stderr and never stdout. README and AGENTS both promise
that with --json stdout is one line of JSON and nothing else, so a notice
printed above that line would break every agent parsing it -- a worse bug
than the stale name being warned about.

The precedence chain was written out twice, once in resolveWebUrl and once in
the MCP server's bootstrap, which is how the two could have drifted. Both now
call configuredWebUrl().

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- Billing enforcement was described as unwired in the status file and in
  BUILD_INSTRUCTIONS. assertPlanAllowsAutonomyTier and assertSeatCapAvailable
  are enforced in store.ts and answer 402. assertSemanticReviewCallAvailable
  really is unwired, but deliberately: review runs on the member's own MCP
  agent and never reaches the service, so metering it would mean adding a
  round-trip purely to bill for it. Recorded as a decision, not a gap. The
  status file is a snapshot of one merge, so it carries a dated correction
  rather than a rewrite.
- architecture.md and onboarding-contracts.md both had WEB_URL falling back to
  "the production default". No such default exists; resolveWebUrl throws
  WEB_URL_REQUIRED. Corrected, and the deprecated fallback documented.
- The verification baseline was stuck at 25 files / 231 tests. Re-measured,
  and reframed as a dated observation with the commands to reproduce it,
  since that is the failure mode of hand-copied counts.
- The CORS tests named a dashboard that no longer exists; they cover the
  opt-in CROSSCODE_ALLOWED_ORIGINS case for any browser client.

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

vercel Bot commented Aug 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
crosscode Ready Ready Preview Aug 4, 2026 6:49am

@amsultan2010
amsultan2010 merged commit bc1fc4c into main Aug 4, 2026
4 checks passed
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