Skip to content

fix(cli): classify a stale project link instead of unknown-error - #4532

Merged
kwakayama merged 4 commits into
mainfrom
fix/issue-1445
Sep 22, 2026
Merged

kwakayama merged 4 commits into
mainfrom
fix/issue-1445

Conversation

@kojiwakayama

@kojiwakayama kojiwakayama commented Sep 20, 2026 •

Copy link
Copy Markdown
Contributor

What was broken

Switching accounts leaves .veryfront/project.json pinned to the previous account's project. The lookup 404s, and both commands raised a bare Error that reached the CLI boundary unclassified as [unknown-error]:

  • cli/shared/deployment/deploy-project.ts:368 — Project "..." was not found. Check the project reference...
  • cli/commands/push/command.ts:1238 — Project "..." was not found. Check .veryfront/project.json...

The two also named the same project by different identifiers: up reported the slug, push reported the uuid, because ProjectReferenceNotFoundError only carried whichever identifier the lookup happened to use (cli/shared/project-resolution.ts:77-86 on main).

What this changes

  • New registry error PROJECT_LINK_STALE (src/errors/error-registry/deploy.ts), status 404, exit code 1, title "Linked project not found".
  • ProjectReferenceNotFoundError now carries both identifiers the directory held, not just the one used for the lookup. describeStaleProjectReference builds one sentence both adapters share: Project "slug" (uuid) was not found. The reference came from .veryfront/project.json; .... A reference that is only an id renders "x" once rather than "x" (x).
    • Correction. An earlier revision of this body said up and push "render byte-identical detail". That is false at the boundary the user sees, and it is retracted. The sentence is byte-identical at the DeployProject adapter, which is the layer cli/shared/deployment/deploy-project.test.ts asserts on; up then re-wraps it as Preview publish failed: <sentence> in describeUpFailure (cli/commands/up/command.ts:179-225) before it reaches the CLI. So push prints the bare sentence and up prints the prefixed one. What does hold end to end — and what the issue's bullet 4 asks for — is that both commands name the project by the same slug + uuid, name .veryfront/project.json, and carry the same [project-link-stale] slug and suggestion (describeUpFailure copies slug/title/suggestion/exitCode for a VeryfrontError; covered by cli/commands/up/command.test.ts "keeps a registry error's classification when Preview publishing fails").
  • The classified error is reserved for the local-link source. This is the fix for the review's major finding, described below.

Review finding addressed (major): project-link-stale was applied to every reference source

The reviewer was right, and I confirmed the failure before fixing it.

Deploy's reference can come from any of the seven sources in cli/shared/config.ts:107-114, but PROJECT_LINK_STALE's title and suggestion are local-link-specific — the suggestion says to remove .veryfront/project.json. The first version classified all of them. Two real failures:

  • veryfront deploy --project <typo> (source argument) told the user to delete this directory's correct link and re-create the project, which forks a duplicate — precisely the hazard the reporter flagged in comment 2.
  • veryfront deploy in a fresh clone (source inferred, no link at all) said "Linked project not found" and pointed at a file that does not exist.

Push had the same defect, narrower: its error.byId guard also matches VERYFRONT_PROJECT_ID and TENANT_PROJECT_ID, which resolve by id (cli/shared/config.ts:733-739) and do not come from the link file.

Both sites now gate on error.source.kind === "local-link" and fall through to the generic, source-neutral message every other source had before this branch. Since .veryfront/project.json requires a non-empty projectId (cli/shared/project-link.ts:92), local-link always implies byId, so this is a strict narrowing with no behaviour change for the case the issue reported.

Regression tests

Four tests, all confirmed to fail without the corresponding source change.

Positive (the issue's case) — these fail on main with [unknown-error]:

  • cli/commands/push/command.test.ts — "classifies a project link this account cannot see as project-link-stale"
  • cli/shared/deployment/deploy-project.test.ts — same name, the up/deploy twin

Negative (the review finding) — added this round:

  • cli/shared/deployment/deploy-project.test.ts — "does not blame the local link for a --project reference that 404s"
  • cli/commands/push/command.test.ts — "does not blame the local link for a VERYFRONT_PROJECT_ID that 404s"

I verified the two negative tests fail without the gate by temporarily replacing the error.source.kind === "local-link" condition with true in both files and re-running:

does not blame the local link for a --project reference that 404s ... FAILED
  AssertionError: Values are not equal: a --project typo must not be classified as a stale local link
  -   true   +   false

does not blame the local link for a VERYFRONT_PROJECT_ID that 404s ... FAILED
  AssertionError: Values are not equal: an environment-supplied project id is not a stale local link
  -   true   +   false

The gate was then restored and both pass.

Gates run

Gate Result
deno task test:file cli/shared/deployment/deploy-project.test.ts ok, 10 passed (131 steps), 0 failed
deno task test:file cli/commands/push/command.test.ts ok, 14 passed (123 steps), 0 failed
deno task test:file cli/shared/project-resolution.test.ts ok, 2 passed (23 steps), 0 failed
deno task test:file src/errors/error-registry.test.ts ok, 2 passed (59 steps), 0 failed
deno check --no-lock on all changed .ts (incl. tests) clean
deno fmt --check on all changed files clean, 7 files
deno task lint clean, 5444 files
deno task lint:test-typecheck baseline holds, 0 new
deno task lint:testing-front-door pass (tests use withMockFetch, no bare globalThis.fetch)
deno task lint:cli-boundary pass
deno task lint:anti-slop baseline ok
deno task lint:test-semantic-dispositions ok
deno task docs + deno task docs:errors regenerated
deno task docs:api-reference:check current, 47 files
deno task docs:errors:check current, 134 errors

deno task test:file runs --no-check, so deno check was run separately on every changed file per the repo's CI-gate guidance.

Re-run after the audit fixes, on the changed files only:

Gate Result
deno task test:file cli/commands/push/command.test.ts ok, 14 passed (123 steps), 0 failed
deno task test:file cli/shared/deployment/deploy-project.test.ts ok, 10 passed (131 steps), 0 failed
deno check --no-lock on the three changed .ts files clean
deno fmt --check on the four changed files clean
deno task docs:errors + docs:errors:check regenerated, current (134 errors), one-line diff
deno task lint:testing-front-door exit 0
deno task lint:anti-slop baseline ok
deno task lint:test-semantic-dispositions ok

Pre-existing failure, not from this branch: deno task docs:validate fails on docs/api-reference/veryfront/agent.md:2004 (createManagedBrokerHandler, a JSDoc starting with "Handle "). That file is untouched here; I confirmed it by stashing this branch's changes and re-running scripts/docs/validate-api-reference.ts on the pristine tree, which reports the identical single issue.

I also kept the generated docs/api-reference/veryfront/errors.md diff to one line: an earlier wording of the PROJECT_LINK_STALE doc comment exceeded the table's 362-char description column and realigned all 150 rows. The comment was shortened to fit the existing width.

Audit findings addressed (this round)

Blocking — the remedy string prescribed a deploy to push users. Fixed in src/errors/error-registry/deploy.ts. The suggestion said "remove .veryfront/project.json and run veryfront up to create and link a project for this account". Both push (cli/commands/push/command.ts:1246) and the deploy adapter raise this error, and veryfront up is not a re-link: upCommand resolves or creates the project itself and then calls deployProject.execute with publish: "live-source" (cli/commands/up/command.ts:371), i.e. it publishes a live Preview deployment. A push or CI user following the remedy performs a deploy they never asked for — and that is a regression in guidance versus main, whose push message only said "remove it to let Veryfront create a project for this directory".

ErrorCreateOptions (src/errors/types.ts) has no suggestion override, so one static string has to serve every raise site; per-command remedies would mean widening the error-framework contract, which is the same reason the "name the signed-in account" finding was declined below. The remedy therefore drops the command it cannot know and keeps only what is true everywhere: unlink, and let push re-create. Correction (record accuracy): that sentence misdescribes what shipped and is retracted. The shipped suggestion does name a command — veryfront push — it only drops veryfront up. What it avoids is naming the command that would turn a re-link into a deploy; naming veryfront push is true at every raise site, because the remedy at both sites is the same unlink, and push completes it on its own. I verified that half — push passes createMissingReference: true (cli/commands/push/command.ts:1231), and with the link gone the source falls back to inferred, which shouldPersistProjectLink (cli/shared/project-resolution.ts:142-144) re-links. New wording:

Run veryfront whoami to check which account is signed in, or remove .veryfront/project.json to unlink this directory and let veryfront push create and link a project for the signed-in account

cli/commands/push/command.test.ts now asserts the rendered remedy names veryfront push and does not name veryfront up. Verified fail-first: with the old suggestion restored, that test fails (13 passed | 1 failed).

Blocking — the false "byte-identical" claim. Retracted above, and the stale comment in cli/shared/deployment/deploy-project.test.ts now says what the assertion actually proves (the adapter boundary) and names describeUpFailure's prefix.

Non-blocking, taken anyway — a near-vacuous negative test. The audit was right that "does not blame the local link for a --project reference that 404s" rested entirely on its one instanceof VeryfrontError === false assertion: with the source gate removed, the classified detail still contains Project "typo-slug" was not found. and still never contains .veryfront/project.json. It now also asserts the generic message's own second sentence, Check the project reference or remove it to let deploy create a project, which the classified detail does not contain. Verified by mutation — gate replaced with true and the instanceof assertion deleted, the test still fails, on exactly that new assertion:

error: AssertionError: Expected actual: "Project "typo-slug" was not found. The reference came
from --project; the project may have been deleted, or it may belong to an account other than the
one you are logged in as." to contain: "Check the project reference or remove it to let deploy
create a project".

The instanceof assertion is kept. One review lens wanted it dropped; dropping it is exactly what would have made this test detect nothing.

Not taken. describeStaleProjectReference's slug !== id collapse branch is unreachable from today's two call sites (both gated on local-link, and isProjectLink requires both identifiers). It is real dead weight, but the fix is deleting behaviour plus its tests, which is not what a fix PR under review should do silently — recorded here instead.

Review findings declined

Minor — "name the signed-in account instead of telling the user to run veryfront whoami". Declined here, deferred to the follow-up issue. The registry suggestion is a static string and ErrorCreateOptions (src/errors/types.ts:57-66) has no suggestion override, so the account can only be named in the detail. Neither raise site holds the identity: cli/shared/deployment/deploy-project.ts and cli/commands/push/command.ts have an API token, not an email. up does hold userInfo from ensureAuthenticated, but it is not plumbed through DeployProjectRequest, and push would need a whoami round trip issued from inside an error path. Widening a shared error-framework contract and adding a network call on a failure path is not a cheap minor fix; it is recorded as a bullet on the follow-up.

Other review findings applied

Minor — suggestion duplicated the detail verbatim. Fixed. The suggestion dropped its leading "The linked project was deleted, or it belongs to an account other than the one you are logged in as", which the detail already says, and now carries only the remedy.

Minor — deferred half of the issue untracked. Fixed. Filed veryfront/veryfront-issue-inbox#1551 covering veryfront up auto-recovery/prompt, a first-class veryfront link <slug>, naming the signed-in account, and recording the owning account in the link so deleted-vs-not-visible can be told apart. It is referenced from the commit message and from the PROJECT_LINK_STALE doc comment.

Final accuracy pass (8153c5d)

Comment-only. When the remedy stopped naming veryfront up, two comments were left quoting the old one: the source-gate comment in cli/shared/deployment/deploy-project.ts and the comment on the negative test in cli/shared/deployment/deploy-project.test.ts both still said project-link-stale tells the user to delete the link and run veryfront up. The hazard they explain is unchanged — a --project <typo> user told to delete this directory's correct link forks a duplicate project — only the command name was stale. No behaviour, no assertions touched.

Gate Result
deno fmt --check on both changed files clean, 2 files
deno check --no-lock on both changed files clean
deno task test:file cli/shared/deployment/deploy-project.test.ts ok, 10 passed (131 steps), 0 failed

Line references in this body were also re-checked against the current tree and three stale ones corrected (createMissingReference at push/command.ts:1231, ErrorCreateOptions at src/errors/types.ts:57-66, and the main constructor at project-resolution.ts:77-86).

Scope note

This ships the error-classification + guidance half of #1445, which is the split the reporter recommended in comment 2. Issue bullets 3 (classify, no unknown-error) and 4 (consistent slug+uuid reference naming .veryfront/project.json) are met. Bullet 2 (up should recover) and the veryfront link <slug> remedy in bullet 1 are #1551 — they need a product decision between auto-create, prompt, and fail, which comment 2 leaves open.

#1445 stays open on merge. This PR is Refs, not Closes. The audit flagged that veryfront-issue-inbox#1551's body said "#1445 is being closed as the error-classification + guidance half only", which would invite someone to close an issue whose headline bullet (up should recover) is untouched. That sentence in #1551 has been corrected.

Refs veryfront/veryfront-issue-inbox#1445

Switching accounts leaves `.veryfront/project.json` pointing at a project
the new token cannot read. The lookup 404s, and both deploy adapters turned
that into a bare `new Error(...)`, which the CLI boundary wrapped as
`[unknown-error]` on the push path and re-wrapped as `[deployment-error]` on
`up`. Neither told the user what to do, and the two adapters named the same
project by different identifiers: deploy printed the configured slug while
push printed the uuid the lookup actually used.

Register `project-link-stale` (DEPLOY, 404, exit code 1) and raise it from
both adapters through one shared wording helper, so `up` and `push` print the
same sentence. `ProjectReferenceNotFoundError` now carries both identifiers
the directory held, not just the one the lookup used, so the detail can name
the project the way the user does and still say which uuid the link records.

Auto-creating or prompting on a mismatch stays out of scope, as the reporter
asked: `ProjectLink` records no owning account, so a deleted project and one
owned by another account are indistinguishable from a 404.

Refs veryfront/veryfront-issue-inbox#1445
Gate the classified error on the reference source. Deploy classified every
ProjectReferenceNotFoundError as project-link-stale, but its suggestion names
.veryfront/project.json, which only the local-link source comes from. A
`--project <typo>` was told to delete this directory's correct link and run
`veryfront up`, forking a duplicate project; a fresh clone with no link was
pointed at a file that does not exist. Push had the same defect more narrowly:
`byId` also covers VERYFRONT_PROJECT_ID and TENANT_PROJECT_ID.

Every other source keeps the generic, source-neutral message it had before.

Also drop the sentence the suggestion duplicated verbatim from the detail.

Refs veryfront/veryfront-issue-inbox#1445
Follow-up: veryfront/veryfront-issue-inbox#1551
@coderabbitai

coderabbitai Bot commented Sep 20, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 42 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: b3482017-d996-4ea4-a771-3a20f78de818

📥 Commits

Reviewing files that changed from the base of the PR and between 76cce66 and 8153c5d.

📒 Files selected for processing (10)
  • cli/commands/push/command.test.ts
  • cli/commands/push/command.ts
  • cli/shared/deployment/deploy-project.test.ts
  • cli/shared/deployment/deploy-project.ts
  • cli/shared/project-resolution.test.ts
  • cli/shared/project-resolution.ts
  • docs/api-reference/veryfront/errors.md
  • docs/guides/errors.md
  • src/errors/error-registry/deploy.ts
  • src/errors/index.ts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gitar-bot

gitar-bot Bot commented Sep 20, 2026 •

Copy link
Copy Markdown

Gitar is working

Gitar

@github-actions

Copy link
Copy Markdown

📦 Client bundle boundary

Entrypoint Modules Source size Server leaks
src/index.client.ts 290 2323 KiB ✅ 0

A server module in a client graph aborts hydration in the browser. New leaks fail CI; known leaks are tracked in scripts/lint/client-bundle-baseline.json to burn down.

@codecov

codecov Bot commented Sep 20, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@kojiwakayama

Copy link
Copy Markdown
Contributor Author

Independent review: 🟡 minor fixes first

Risk if merged as-is: low | Reviewer confidence: high

Look at this first

The suggestion string on PROJECT_LINK_STALE in src/errors/error-registry/deploy.ts -- read it as a veryfront push user, not an up user. Both commands raise this error, but the single static suggestion says 'remove .veryfront/project.json and run veryfront up'. up publishes a live Preview deployment; push does not. I verified push recovers on its own once the link is deleted (createMissingReference: true, cli/commands/push/command.ts:1228), so the remedy actively over-prescribes a deploy for the CI/push audience -- and origin/main's push message did not. No test or CI gate catches a misdirecting-but-well-formed remedy string, and this is the one piece of behaviour the PR actually ships to users; everything else in the diff is classification plumbing that I confirmed correct. Decide whether the registry's static-suggestion design is acceptable here or whether the clause must be dropped.

Blocking

  1. Fix the shipped suggestion string in /Users/agent1/code/veryfront/veryfront-code/src/errors/error-registry/deploy.ts (PROJECT_LINK_STALE): it tells EVERY affected user to 'remove .veryfront/project.json and run veryfront up'. push raises the same error (cli/commands/push/command.ts:1245), and veryfront up does not just re-link -- it creates the project AND publishes a live Preview deployment (cli/commands/up/command.ts calls deployProject.execute with publish: 'live-source'). A push/CI user who follows the remedy performs a deploy they never asked for. push already self-heals once the link is gone -- verified it passes createMissingReference: true at cli/commands/push/command.ts:1228 -- so the correct remedy for push is just 'remove the file and run veryfront push again'. This is also a regression in guidance vs origin/main, whose push message said 'remove it to let Veryfront create a project for this directory'. One-line fix; if the registry's static-suggestion design genuinely blocks a per-command remedy, drop the 'and run veryfront up' clause rather than name the wrong command.
  2. Correct the false claim in the PR body and in the test comment at cli/shared/deployment/deploy-project.test.ts:737: 'up and push render byte-identical detail' is false at the boundary the user sees. CONFIRMED -- describeUpFailure (cli/commands/up/command.ts:179-225) rebuilds the detail as Preview publish failed: ${sentence} before it reaches the CLI, so up prints a prefixed sentence and push prints the bare one. Byte-equality holds only at the DeployProject adapter, which is the only layer the test asserts. The underlying issue bullet 4 (same recognisable slug+uuid reference in both commands) IS met; only the wording claim is wrong. Zero-cost to fix, and this pipeline has shipped false 'byte-for-byte' claims before, so the body should not carry another one into the merge record.

Non-blocking

  • SCOPE IS AN HONEST PARTIAL, BUT WATCH THE CLOSURE. Issue bullet 2 -- 'veryfront up should recover' -- is not implemented, and it is in the issue title. The split IS sanctioned by the genuine reporter (mattboon, comment 2: 'keep Release veryfront-code 0.1.393 #1445 as the error-classification + guidance fix ... split the auto-recover/prompt/link-command behavior into a follow-up'), NOT by the automated triage comment posted under @kojiwakayama's account, which carries its own 'do not cite as acceptance-criteria authority' footer. The PR uses 'Refs', which is correct. THE RISK: veryfront-issue-inbox#1551's body states 'Release veryfront-code 0.1.393 #1445 is being closed as the error-classification + guidance half only'. A human acting on that sentence closes an issue whose headline bug ('no auto re-link/create') is untouched. Decide consciously whether Release veryfront-code 0.1.393 #1445 stays open on merge.
  • Acceptance mapping, verified bullet by bullet: (1) name the account -- NOT met, message says 'an account other than the one you are logged in as' and points at veryfront whoami; declined with a reason I confirmed (ErrorCreateOptions in src/errors/types.ts has no suggestion override, neither raise site holds an email). (1b) veryfront link <slug> remedy -- NOT met, no such command exists anywhere in cli/commands/. (2) up recovers -- NOT met, deferred to Add hosted agent service runtime helper #1551 (confirmed open). (3) classify the error -- MET for the local-link source only; --project, veryfront.json, VERYFRONT_PROJECT_ID, module/environment config and inferred references still raise a bare Error and still render [unknown-error]. (4) consistent slug+uuid reference naming the file -- MET in both commands.
  • THE FIX DOES REACH veryfront up -- I confirmed this independently rather than trusting the PR. execute() calls ensureProjectLinkedForDeploy (deploy-project.ts:1872) before the push-source step (:1920); upCommand passes no explicit projectSlug to deployProject.execute, so explicitProjectSlug is undefined and the source stays local-link rather than flipping to argument; and describeUpFailure copies error.slug/category/status/title/suggestion/exitCode for a VeryfrontError. So up really does print [project-link-stale]. Treat the missing up test as a coverage gap, not a behaviour gap.
  • COVERAGE PADDING, CONFIRMED (the pipeline's known incentive under SonarCloud's >=80% new-code gate). Two of the three assertions in the new deploy negative test 'does not blame the local link for a --project reference that 404s' are vacuous. I traced both: with the gate removed, the classified detail for the argument source renders Project "typo-slug" was not found. The reference came from --project; ..., which still satisfies the assertStringIncludes, and origin/main's generic deploy message never contained '.veryfront/project.json' either. That test rests entirely on its single instanceof VeryfrontError === false assertion. Suggested fix: assert error instanceof VeryfrontError ? error.slug !== 'project-link-stale' : true, or assert the message names '--project'.
  • Separately padded: describeStaleProjectReference's identifier-collapsing branch (slug && id && slug !== id / the ?? id ?? error.reference fallback) is unreachable in production. Both call sites are gated on source.kind === 'local-link' (push/command.ts:1243, deploy-project.ts:378) and isProjectLink (cli/shared/project-link.ts:86-94) requires BOTH projectId and projectSlug non-empty -- I read both. The two project-resolution tests covering that branch hand-build fixtures with tenant-environment and json-config sources, i.e. exactly the sources the gate excludes. Branches that exist because the tests exist.
  • 404 CONFLATES 'not found' WITH 'you cannot see it' -- real, but PRE-EXISTING, and I am downgrading how one reviewer framed it. I confirmed the server side on veryfront-api origin/main: project-resolver.ts rewrites a 403 to sendNotFound when canSeeProject is false, and checkProjectAccess returns 'not_member' (-> 404) for any project-scoped token whose projectId differs. So a scoped or under-privileged token gets told its correct link is stale. BUT origin/main already said 'Check .veryfront/project.json or remove it to let Veryfront create a project for this directory' on the same 404 -- this PR does not introduce or widen the hazard, it narrows classification by source and makes the existing advice more authoritative. Belongs on Add hosted agent service runtime helper #1551 (which already tracks 'record the owning account in the link'), not on this PR.

Where the reviewers disagreed

Four real conflicts; I checked each against origin/main and ruled.

  1. THE BIGGEST ONE -- the instanceof VeryfrontError === false assertion in the two negative tests. The contract/test-quality lens calls it over-coupling and wants it dropped ('it pins the [unknown-error] degradation that Add hosted child execution log writer #1442 wants removed'). The security lens relies on it as the only assertion that actually fails when the gate is removed. THE SECURITY LENS IS RIGHT AND THE CONTRACT LENS IS WRONG, and I can show it: for the deploy test, if you delete the gate, describeStaleProjectReference renders Project "typo-slug" was not found. The reference came from --project; ... for the argument source -- so the assertStringIncludes(message, 'Project "typo-slug" was not found.') assertion STILL PASSES, and origin/main's generic deploy message never contained '.veryfront/project.json' so that assertion passes too. Drop the instanceof line and that test detects nothing at all. Keep it (or replace it with a slug-not-equal assertion); do not follow the contract lens here.

  2. Same tests, opposite direction: the security lens says the push negative test's path assertion 'is not vacuous, because push's generic message interpolates the source name'. Wrong -- in that test the source name IS 'VERYFRONT_PROJECT_ID', so the path can never appear either way. But push's test survives regardless, because its OTHER assertion (Check VERYFRONT_PROJECT_ID) is genuinely load-bearing: the classified detail would say 'The reference came from VERYFRONT_PROJECT_ID', not 'Check'. Net: push's negative test rests on two real assertions, deploy's on exactly one.

  3. The security lens presents the 404-conflation (scoped/under-privileged tokens get told their correct link is stale) as a gap this PR leaves open on the 'cause axis'. The framing overstates the delta. I confirmed the server behaviour it describes is real (veryfront-api project-resolver.ts rewrites 403 to 404 via canSeeProject; checkProjectAccess returns not_member for scoped tokens), but origin/main ALREADY told those users to remove the link on the same 404. This PR narrows by source and changes no 404 handling. Status quo, not a new hazard -- route it to Add hosted agent service runtime helper #1551, do not hold the PR for it.

  4. The correctness lens claims it walked the whole up path; the other two treat 'no up test' as leaving the behaviour unproven. The correctness lens is right and I reproduced its trace independently: execute() runs ensureProjectLinkedForDeploy at deploy-project.ts:1872 before push-source at :1920, upCommand passes no explicit projectSlug so the source stays local-link instead of flipping to argument, and describeUpFailure copies error.slug. up does emit [project-link-stale]. The missing test is a genuine coverage gap -- a regression in describeUpFailure's VeryfrontError branch would re-open Release veryfront-code 0.1.393 #1445 with all four new tests green -- but nobody should read it as doubt about whether the fix works.

Where all three agreed and I confirmed: the 'byte-identical detail' claim is false at the command boundary, and the scope split is authorised by the genuine reporter's comment 2 rather than by the automated triage comment wearing @kojiwakayama's identity. All three lenses correctly refused to treat that triage comment as acceptance authority.

On the pipeline's fail-first question: two lenses said 'yes', one said 'could-not-check'. I did not execute the suite either, but origin/main raises a plain new Error(...) at both sites (push/command.ts:1238, deploy-project.ts:368), so assertInstanceOf(error, VeryfrontError) in both positive tests cannot pass on main. The fail-first claim holds by source inspection for the positive tests; for the deploy negative test it holds only through the one assertion discussed in (1).


Provenance: independent automated review (Claude Code) via @kojiwakayama's token. Tooling output, not a human approval. Three reviewers examined this diff through separate lenses (correctness, security/authz, contract & test quality); a synthesis pass adjudicated their disagreements against origin/main and dropped findings that did not survive. Verify before acting.

PROJECT_LINK_STALE told every affected user to "remove .veryfront/project.json
and run veryfront up". push raises the same error, and `veryfront up` is not a
re-link: upCommand resolves or creates the project and then calls
deployProject.execute with publish: "live-source", so a push or CI user who
follows the remedy performs a live Preview deployment they never asked for.
origin/main's push message prescribed no command at all, so this was also a
regression in guidance.

ErrorCreateOptions carries no suggestion override, so one static string has to
serve every raise site. The remedy now keeps only what is true at all of them:
unlink, and let veryfront push create and link a project for the signed-in
account. push passes createMissingReference: true, and with the link gone the
source falls back to `inferred`, which shouldPersistProjectLink re-links.

The push regression test now asserts the rendered remedy names veryfront push
and does not name veryfront up. Verified fail-first: with the old suggestion
restored the test fails on that assertion.

Also, two evidence fixes the audit was right about:

- The "byte-identical detail" comment in deploy-project.test.ts was false at
  the boundary the user sees. describeUpFailure re-wraps the shared sentence as
  `Preview publish failed: <sentence>` before `up` prints it, so byte-equality
  holds at the DeployProject adapter only. The comment now says that, and the
  PR body's matching claim is retracted.
- The `--project` negative test rested entirely on its instanceof assertion:
  with the source gate removed, the classified detail still contains
  `Project "typo-slug" was not found.` and still never contains
  .veryfront/project.json. It now also asserts the generic message's own second
  sentence, which the classified detail does not contain. Verified by mutation
  with the instanceof assertion deleted as well: the test still fails.

Refs veryfront/veryfront-issue-inbox#1445
Follow-up: veryfront/veryfront-issue-inbox#1551
@kojiwakayama

kojiwakayama commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor Author

Audit follow-up pushed as a89f9d0. Both blocking items were reproduced before being touched; one non-blocking item was taken as well, and one was declined.

Fixed — the remedy prescribed a deploy to push users (blocking)

Confirmed. PROJECT_LINK_STALE's suggestion said "remove .veryfront/project.json and run veryfront up". Both raise sites reach it — cli/commands/push/command.ts:1245 and the deploy adapter — and veryfront up is not a re-link: upCommand resolves or creates the project itself and then calls deployProject.execute with publish: "live-source" (cli/commands/up/command.ts:371). A push or CI user who follows the remedy publishes a live Preview deployment they never asked for. origin/main's push message named no command at all, so this was a regression in guidance, not just a rough edge.

ErrorCreateOptions has no suggestion override, so one static string serves every raise site and a per-command remedy would mean widening the error-framework contract — the same reason the "name the signed-in account" finding was declined earlier in this PR. So the clause is dropped rather than replaced with a different command name. Correction: that sentence is wrong about what shipped and is retracted. The clause was replaced, not dropped: the shipped suggestion names veryfront push. What was dropped is veryfront up, the command that would have turned a re-link into a deploy. Naming veryfront push is safe at every raise site because the remedy is the same unlink everywhere and push completes it on its own — which is exactly what the quoted string below says. The remedy now keeps only what is true at every raise site:

Run veryfront whoami to check which account is signed in, or remove .veryfront/project.json to unlink this directory and let veryfront push create and link a project for the signed-in account

I verified the self-heal half rather than assuming it: push passes createMissingReference: true (command.ts:1229), and with the link gone the source falls back to inferred, which shouldPersistProjectLink (project-resolution.ts:142-144) re-links.

cli/commands/push/command.test.ts now asserts the rendered remedy names veryfront push and does not name veryfront up. Fail-first, observed: restoring the old suggestion turns that test red (13 passed | 1 failed).

Fixed — the false "byte-identical detail" claim (blocking)

Confirmed and retracted. describeUpFailure (cli/commands/up/command.ts:179-225) rebuilds the detail as Preview publish failed: ${sentence} before it reaches the CLI, so push prints the bare sentence and up prints the prefixed one. Byte-equality holds only at the DeployProject adapter, which is the single layer the test asserts. The PR body now says that, and the test comment at deploy-project.test.ts says what its assertion actually proves and names the prefix. The underlying issue bullet — both commands giving the same recognisable slug + uuid reference and naming the link file — is genuinely met; only the wording was overclaiming.

Fixed — the --project negative test was nearly vacuous (non-blocking, taken)

Also confirmed. With the source gate removed, the classified detail is Project "typo-slug" was not found. The reference came from --project; ... — which still satisfies the assertStringIncludes and still never contains .veryfront/project.json. The test rested entirely on its one instanceof assertion.

It now also asserts the generic message's own second sentence, Check the project reference or remove it to let deploy create a project, which the classified detail does not contain. Verified by mutation — gate replaced with true and the instanceof assertion deleted:

error: AssertionError: Expected actual: "Project "typo-slug" was not found. The reference came
from --project; ..." to contain: "Check the project reference or remove it to let deploy create
a project".

The instanceof assertion is kept. One review lens wanted it dropped as over-coupling; dropping it is precisely what would have left this test detecting nothing.

Declined — removing the unreachable identifier-collapse branch (non-blocking)

The finding is factually right: describeStaleProjectReference's slug && id && slug !== id branch cannot be reached from today's two call sites, since both gate on local-link and isProjectLink requires both identifiers. But satisfying it means deleting exported behaviour plus the two tests that cover it, in a PR that is under review for a different change. Recorded in the body instead; it is a cleanup, not a defect in what ships here.

Scope / closure

#1445 stays open on merge — this PR is Refs, not Closes. veryfront-issue-inbox#1551's body previously read "#1445 is being closed as the error-classification + guidance half only", which invited someone to close an issue whose headline bullet (veryfront up should recover) is untouched. That sentence has been corrected in #1551.

Gates

deno task test:file on cli/commands/push/command.test.ts (14 passed) and cli/shared/deployment/deploy-project.test.ts (10 passed); deno check --no-lock and deno fmt --check on the changed files; deno task docs:errors regenerated (one-line diff) and docs:errors:check current; lint:testing-front-door, lint:anti-slop, lint:test-semantic-dispositions all clean.

On CI: 43 checks pass, 0 fail. All three SonarCloud checks pass (Code Analysis, scan, quality gate). The two still pending — Automated review and quality gate (merge) — are waiting on review because the PR is still a draft, not on a result.

The remedy on PROJECT_LINK_STALE no longer names `veryfront up`, but the
source-gate comment in deploy-project.ts and the negative test's comment
still described it that way. The hazard they explain is unchanged -- a
`--project <typo>` user told to delete this directory's correct link
forks a duplicate project -- only the command the suggestion names was
stale.

Refs veryfront/veryfront-issue-inbox#1445
@sonarqubecloud

Copy link
Copy Markdown

@kojiwakayama

Copy link
Copy Markdown
Contributor Author

State of this PR for a reviewer

Head 8153c5d70c. 44 checks pass, 0 fail, including SonarQube Cloud quality gate, coverage gate and all four coverage shards. The one pending check is Automated review — PR#4532 draft waits for review, which is waiting on the draft flag, not on code.

What this round changed

Comment- and prose-only; no behaviour, no assertions touched.

  • Two stale comments removed. When the remedy stopped naming veryfront up, two comments were left quoting the old one: the source-gate comment in cli/shared/deployment/deploy-project.ts and the comment on the --project negative test in cli/shared/deployment/deploy-project.test.ts both still said project-link-stale tells the user to delete the link and run veryfront up. The hazard they explain is unchanged; only the command name was stale.
  • Three stale line references in the body corrected — createMissingReference is at push/command.ts:1231 (body said 1229), ErrorCreateOptions at src/errors/types.ts:57-66 (said 56-65), and the main constructor at project-resolution.ts:77-86 (said 273-281, which are the current raise sites, not main's class).

Claims in the body I re-verified against the tree, rather than trusting

  • The shipped PROJECT_LINK_STALE.suggestion no longer names veryfront up, and the regenerated docs/guides/errors.md carries the same string — the generated docs are not stale against the registry.
  • Fail-first, re-run by mutation. Restoring the old suggestion in src/errors/error-registry/deploy.ts fails cli/commands/push/command.test.ts at exactly the claimed ratio: FAILED | 13 passed (122 steps) | 1 failed (1 step).
  • The --project negative test is no longer near-vacuous. With the source gate replaced by true and the instanceof VeryfrontError assertion deleted, the test still fails, on the added assertion and with the error text the body quotes: Expected actual: "Project "typo-slug" was not found. The reference came from --project; ..." to contain: "Check the project reference or remove it to let deploy create a project". Both mutations were reverted; the tree is clean and nothing is left unpushed.
  • cli/commands/up/command.test.ts → "keeps a registry error's classification when Preview publishing fails" exists and does assert that describeUpFailure copies slug and suggestion and prefixes the detail with Preview publish failed:, which is what the body cites it for.
  • veryfront-issue-inbox#1551 no longer says Release veryfront-code 0.1.393 #1445 is being closed by this PR; it states the opposite and is open.

One CI failure that was not this branch

The first run on this head failed coverage shard 4/4 on src/config/loader.test.ts → "coalesces a preview burst for one retained source snapshot into one read and evaluation" (reads was 2, expected 1). That file is untouched here. The test releases a blocked read after a fixed 50 ms sleep and asserts the whole burst was admitted inside that window, so a loaded runner can admit one request late; it passes locally (1 passed (409 steps)) and passed on re-run. Worth a flake ticket against the owner of #4522, but it is not a signal about this diff.

Deliberately deferred, and where it is tracked

  • The recovery half of Release veryfront-code 0.1.393 #1445 (veryfront up auto-create/prompt, a first-class veryfront link <slug>, naming the signed-in account, recording the owning account so deleted-vs-not-visible can be told apart) → veryfront-issue-inbox#1551, open. This PR is Refs, not Closes.
  • 404 conflation for scoped/under-privileged tokens (the API rewrites 403 to 404, so a correct link is reported stale). Pre-existing on main, unchanged here, covered by the deleted-vs-not-visible bullet in Add hosted agent service runtime helper #1551.
  • No up-level test for this specific slug. The describeUpFailure VeryfrontError branch is already covered by the existing up test named above; a near-duplicate that swaps in PROJECT_LINK_STALE would add a coverage number and no new signal, so it was not written. Flagging it rather than padding.
  • describeStaleProjectReference's identifier-collapsing fallback (slug ?? id ?? error.reference) is unreachable from today's two call sites, since both are gated on local-link and isProjectLink requires both identifiers. It is reachable only through the exported helper's own unit tests. Left in place as a defensive branch on an exported function.

What I need from you

  1. The remedy wording. ErrorCreateOptions has no per-site suggestion override, so one static string serves both raise sites, and it currently names veryfront push — true and non-destructive at every site, but a veryfront up or veryfront deploy user is pointed at a command they did not run. Pick one: (a) keep veryfront push; (b) drop the command name entirely ("remove .veryfront/project.json to unlink this directory, then run your command again"); (c) widen ErrorCreateOptions with a per-raise-site suggestion, which is a change to the shared error framework and belongs in Add hosted agent service runtime helper #1551. (a) is what is shipped; I did not churn it again on my own judgement.
  2. Scope ratification. This merges the error-classification + guidance half only, with Release veryfront-code 0.1.393 #1445 left open until the recovery behaviour lands — the split the reporter recommended in comment 2. Confirm that is acceptable, or say the recovery half must ship in the same PR.
  3. Draft → ready for review, and merge. Both are yours. The Automated review check will not run while this is a draft.

@kwakayama

Copy link
Copy Markdown
Contributor

Review score: 94/100\n\nReviewed head: \n\nThe change cleanly separates stale local-link classification from ordinary project-reference failures, preserves project identity and source context, and prevents a stale-link error from prescribing to push users. The error registry, CLI paths, docs, and focused regressions are aligned.\n\nValidation:\n- 10 files changed, with focused push, deployment, and project-resolution coverage\n- CI, coverage, Sonar, CodeQL, lint, format, and typecheck pass\n- No unresolved review threads remain\n- The PR is still draft, so the Automated review gate is waiting on readiness\n\nScore is 94/100. Promote to ready for review so the final exact-head gate can run.

@kwakayama

Copy link
Copy Markdown
Contributor

Review score correction: 94/100

Reviewed head: 8153c5d70cd3e65d55c4b6ddf745789343ee3a48

The change separates stale local-link classification from ordinary project-reference failures, preserves project identity and source context, and prevents a stale-link error from prescribing veryfront up to push users. Error registry, CLI paths, docs, and focused regressions are aligned.

Validation:

  • 10 files changed, with focused push, deployment, and project-resolution coverage
  • CI, coverage, Sonar, CodeQL, lint, format, and typecheck pass
  • No unresolved review threads remain
  • PR is still draft, so Automated review is waiting on readiness

The PR scores 94/100. Promote to ready for review so the final exact-head gate can run.

@kwakayama
kwakayama marked this pull request as ready for review September 22, 2026 06:36
@kwakayama
kwakayama self-requested a review as a code owner September 22, 2026 06:36
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for security reviews. Please try again later.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Your trial has ended. Reactivate Greptile to resume code reviews.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review 🔄 Running since 2026-09-22T06:36:42.976997Z 8153c5d Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions

Copy link
Copy Markdown

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🎉

Reviewed commit: 8153c5d70c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@kwakayama
kwakayama added this pull request to the merge queue Sep 22, 2026
Merged via the queue into main with commit b1d721f Sep 22, 2026
116 of 120 checks passed
@kwakayama
kwakayama deleted the fix/issue-1445 branch September 22, 2026 07:05
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.

2 participants