Issue 181 CI Green Blockers Architecture Research
Issue 181 CI Green Blockers Architecture Research
Executive answer
The delivery was not stuck on TypeScript at closeout. The final pull-request run completed all 30 build, type-check, test, and browser tasks successfully. Its only product failure was one unused JavaScript import found by the root lint gate.
The prolonged failure came from validation and authority architecture:
- the fast root lint gate ran after roughly 16 minutes of expensive work;
- workspace-targeted lint excluded the file that root lint later rejected;
- focused test selectors could succeed while selecting no tasks or no tests;
- scaffold adoption preview exercised a full isolated dependency and lint lifecycle during feature convergence;
- host resource failures had no typed environment classification;
- implementation, manual validation, hosted CI, merge, and release state were recorded by separate authorities that could disagree; and
- GitHub allowed the pull request to merge while its behavior check was still pending and later failed.
The code did merge. Pull request #187 merged at 2026-09-03 03:19:27 UTC. The delivery goal remained paused because its stronger closeout contract still required green behavior CI and a manually trusted live Codex App hook observation. That distinction was not surfaced promptly.
Research coverage
Four readonly lanes covered:
| Lane | Scope |
|---|---|
| Provider authority | Pull-request timing, branch protection, behavior and release workflows, merge-tree identity |
| Validation feedback | CI ordering, Turbo selection, lint ownership, zero-test selectors, candidate preview |
| Scaffold ownership | Baseline identity, managed receipts, project-generated ownership, legacy path heuristics |
| Execution health | Process spawning, EAGAIN, resource pressure, validation failure taxonomy |
The coordinator also replayed the two lint entrypoints against the final tree, read the live goal state, inspected all six pull-request workflow runs, and compared the plan and implementation evidence with hosted CI.
A second four-lane readback after merge separately audited the provider
timeline, workflow architecture, validation parity, and delivery closeout
state. It confirmed the same tree and failure, and corrected the meaning of the
green main action: it was release authority, not product verification.
Current state
- Branch
team/stefan/fix/ultracite-lint-configis atb5670d64. - Pull request #187 is squash-merged as
fc45df6d. - The branch and merge commit have the same tree,
26b4c7a. History differs because GitHub squashed the branch; product bytes do not. - No changelog path changed, so release classification is
none. - The final behavior run #33710893295 is red.
- The post-merge release-authority run #33710946802 is green. It proves a different contract and does not supersede behavior CI.
- The delivery goal is paused after 33,922 seconds of execution. The plan is marked completed and implementation notes say automated implementation is complete, while the same notes retain the live Codex App observation as a manual gate.
Proven failure sequence
Six behavior runs failed on successive candidate commits:
| Run | Duration | First failing surface |
|---|---|---|
| 33631016739 | 2m 14s | Wiki build in the combined build/type/test/browser command |
| 33631857836 | 10m 14s | Scaffold/update behavior assertions and dependency-version conflicts |
| 33646489057 | 12m | Twenty CLI behavioral failures around Effect prepare and receipt ownership |
| 33705048492 | 17m 47s | One remaining CLI behavioral portfolio assertion |
| 33709718056 | 16m 18s | Three packages/config lint findings |
| 33710893295 | 17m 30s | One root no-unused-vars finding |
This is serial fault discovery. Each repair exposed the next validation layer only after another long hosted run.
Architectural findings
1. Static feedback is ordered last
The behavior workflow runs build check-types test test:browser first and only
then runs lint check (.github/workflows/behavior-contract.yml:48-49). The
root package has no single script that reproduces this hosted graph. A one-line
unused import therefore waited behind the complete build and browser portfolio.
The final run proves the cost: all 30 first-wave tasks passed, then root lint
failed on apps/cli/src/data/hooks/format-edited-file.mjs:17.
Lint is also redundantly owned. Package check scripts already compose lint
and format, while CI separately asks Turbo for lint and check. This repeats
work and makes the failing authority harder to identify.
2. The local lint surfaces do not mean the same thing
The CLI workspace config explicitly ignores
src/data/hooks/format-edited-file.mjs (apps/cli/oxlint.config.ts:1). Running
workspace-targeted Oxlint against that file returns No files found to lint.
The root correctness config does not ignore it (.oxlintrc.json:1), and this
command immediately reproduces CI:
bunx oxlint --config .oxlintrc.json \
apps/cli/src/data/hooks/format-edited-file.mjs
apps/cli/src/data/hooks/format-edited-file.mjs:17:3:
Identifier 'unlinkSync' is imported but never used.Commit c9157865 replaced the only unlinkSync call with rmSync but retained
the import. The same commit updated implementation notes to say targeted
Oxlint passed. The evidence was therefore generated against a lint surface
that did not cover this file, or was not reread after the final change.
This is especially important for issue 181: the new edited-file feedback path could not prove its own canonical source clean through the workspace lint configuration used for targeted validation.
3. Selection success is not execution success
Turbo can return success with an empty affected graph. A readonly probe using
equal base and head SHAs returned tasks: [] and status zero. The workflow
contract test checks command text, not the selected Turbo tasks or executed
test count (scripts/behavior-contract/affected-verification.test.ts:14-30).
The issue plan also retained a stale Vitest title selector. Its documented
selector at PLAN.md:692-704 no longer matches the test title at
lint-feedback.test.ts:61-62, so the command matched zero tests. A later
verification had to inspect actual test counts before its result was useful.
4. Feature validation is coupled to a migration-scale preview
Candidate lint preview materializes an isolated candidate, installs its
dependencies, optionally patches Effect, and runs a full-tree Oxlint JSON scan
(apps/cli/src/runtime/scripts.ts:458-674). The measured Effect lifecycle alone
was about 45 seconds. Existing lint debt can dominate its output, and buffer
overflow degrades structured diagnostics to reduced text-derived fields.
That full preview is appropriate at the update publication boundary. Reusing it as routine inner-loop confidence makes a focused lint-feedback delivery pay the cost and failure surface of dependency resolution, candidate isolation, configuration discovery, full repository traversal, JSON capture, and cleanup.
5. Managed-state ownership is explicit but not singular
The explicit model distinguishes scaffold-managed files from current, missing,
and stale project-generated files using producer, revision, authority path, and
fingerprints (packages/scaffold/src/baseline/managed-file.ts:33-91 and
apps/cli/src/features/scaffold-state/ownership.ts:8-85).
Update still layers path-based wiki, Claude, and project-authored heuristics on
top of that result (apps/cli/src/update/run.ts:1640-1717 and :1762-1893).
Package dependency desire is compiled structurally, while permission to repair
versions depends on receipts (apps/cli/src/scaffold/output.ts:992-1055 and
:1648-1789). Acceptable bytes without the expected receipt can therefore
conflict with the desired model.
The checkout also has two version records. .devpunks/settings.json:5-6 and
the manifest baseline block at .devpunks/scaffold-manifest.json:2103-2123
record baseline 2026.09.02-ec9276e6, while a newer stable baseline
2026.09.02-07c7bd6f exists. hi check consequently reports baseline and
local-edit drift that is separate from PR #187's behavior failure.
One manifest currently carries baseline provenance, file hashes and modes,
dependency receipts, structured entries, and degradations
(apps/cli/src/update/run.ts:2464-2492). Settings separately carries baseline
and CLI versions alongside user/provider choices and tools
(apps/cli/src/features/project-settings/model.ts:15-35). These are distinct
truths presented through one broad notion of “drift.”
6. Host failures are not typed separately from repository failures
The local hi check --json failure spawnSync tar EAGAIN occurred while the
host process table was exhausted by thousands of zombie children owned by a
Raycast Backend process. Shell forks also failed. Later readback showed the
host had recovered to 934 processes and 30 zombies, although load remained
high. This change without repository mutation confirms a transient execution
environment failure.
The validators throw raw spawn errors or collapse them into broad operation failures:
scripts/validate-consumer-repositories.mjs:441-460throwsspawnSyncerrors verbatim; tar is invoked at:948and:1265.scripts/behavior-contract/consumer-repositories.test.ts:1060-1087invokesBun.spawnSync(["tar", ...])without a typed startup-error branch.scripts/validate-cutover-repeat.mjs:491-519rejects raw start errors or generic nonzero exits, while:703-711omitserror.codefrom inspectable failure evidence.- Candidate preview can classify a resource-starved dependency spawn as a
dependency failure and broad unexpected exceptions as config-load
(
apps/cli/src/runtime/scripts.ts:510-529,:577-619, and:755-759). - Public lint-preview failure kinds have no environment or resource class
(
apps/cli/src/presentation/operation-result/lint-preview-facts.ts:16-31).
Retries are not a sufficient fix. Retrying under process exhaustion can add
pressure. The missing contract is a preflight plus preserved startup errno and
an environment-blocked result distinct from repository failure.
7. Merge authority, behavior authority, and release authority disagree
The final behavior run started at 03:18:50 UTC. Pull request #187 merged at 03:19:27, while that run was still active. The run failed at 03:36:20. This proves that Stable Aggregate Check did not prevent this merge.
The branch-protection and repository-ruleset APIs now return HTTP 403 with
Upgrade to GitHub Pro or make this repository public to enable this feature.
The exact protection configuration therefore cannot be read or changed under
the repository's current private Free plan. Repository workflow changes can
make the aggregate accurate and observable, but cannot make it a provider-
enforced merge prerequisite under that plan.
The checked prospective merge ref and the final squash merge have the same tree, so candidate selection was correct. Enforcement was absent.
The behavior workflow triggers only on pull_request
(.github/workflows/behavior-contract.yml:3-5). No push-to-main behavior run
revalidated the final SHA. The release workflow does run on main, but it owns
release classification and publication authority, not build/type/test/lint
behavior. A green release-authority run and red behavior run can therefore
coexist legitimately.
8. Delivery state has no single closeout state machine
The repository and control plane simultaneously reported:
- plan status: completed;
- implementation notes: automated implementation complete;
- live Codex App hook: not observed and still manual;
- pull request: merged;
- behavior CI: failed;
- release authority: passed;
- delivery goal: paused, not complete.
Each statement is locally accurate, but the operator-facing result was “nine hours and no merge.” The missing abstraction is a delivery state that exposes these authorities side by side and says which one prevents final closeout.
Synthesized target architecture
The next design should establish four explicit contracts:
host preflight
-> fast static authority (root lint + format)
-> affected type/build/test authority with non-empty execution evidence
-> heavy browser/candidate integration authority
-> stable aggregate required by merge
each gate -> pass | repository-failure | environment-blocked | manual-required
delivery state -> implemented -> locally verified -> manually observed
-> hosted verified -> merged -> released/not-applicableRecommended direction
- Put root static checks first or run them in parallel as a separately visible job. They should finish in seconds and fail before browser installation.
- Define one canonical lint owner per file. Exclusions must not make the recommended changed-file command silently skip a file covered by root CI.
- Add executed-task and executed-test counts to verification evidence. Empty
selection must be an explicit
not-applicabledecision, never implicit success. - Expose one local command that is byte-for-byte equivalent to the hosted behavior graph, including root correctness checks and exact base/head semantics.
- Keep candidate adoption preview at the update boundary. Cache it by exact candidate tree/toolchain identity or use narrow deterministic contract tests in the inner loop, with one full integration witness at closeout.
- Separate host/resource startup failures from repository assertions. Preserve errno, syscall, command, args, and cwd; run a read-only health preflight before subprocess-heavy validators.
- Separate baseline availability, last-applied baseline, managed-file receipt, project edit, provider receipt, and generated-output drift in the model and CLI presentation.
- Require Stable Aggregate Check on
main. Add a post-merge behavior run as defense in depth if default-branch health must remain observable after administrative or emergency merges. - Treat the Codex App trust/observation as
manual-required. Either make it a true merge gate with an operator acknowledgement artifact or move it to a separately tracked platform validation. Do not leave it as prose inside an otherwise completed implementation record. - Make delivery closeout reconcile provider, CI, merge, release, manual, and local evidence before changing goal status. A paused goal must name the exact remaining authority immediately.
Facts, inferences, and uncertainty
Facts
- Type-check and all other first-wave tasks passed in the final run.
- Root lint failed on one unused import.
- Workspace-targeted lint excludes that file; root lint covers it.
- Six consecutive hosted behavior runs failed on successively later layers.
- PR #187 merged while final behavior CI was pending;
mainhas no required behavior gate that prevented that merge. - The live Codex App hook observation was never completed.
- Local process exhaustion produced
EAGAIN; later host state improved without a repository change.
Inferences
- The dominant elapsed time came from late and non-equivalent feedback, not the complexity of the final defect.
- “Automated implementation complete” was recorded too early to act as delivery completion evidence.
- Baseline drift and host pressure prolonged diagnosis because both were presented near repository defects without distinct authority classes.
Unresolved decisions
- Is live Codex App observation required for merge, or is it a separately acknowledged platform gate?
- Should host preflight fail closed for all heavy gates or only classify the result as environment-blocked?
- Should the root correctness config or workspace config own managed hook source, and how should the same command be exposed to agents?
- If the current provider plan cannot enforce a required aggregate, which external authority should own the procedural merge gate?
- Should resource health be a public operation failure variant or orthogonal execution metadata?
- Which receipt is authoritative for last-applied baseline versus latest available baseline?
Next local action
The user authorized the first independently valuable repair slice: fix the
unused import, make root static validation an early independently visible job,
run behavior verification on every main push, and aggregate static and heavy
verification. The affected entrypoint first applies each directly changed
non-wiki workspace's own Oxlint and Oxfmt configuration to changed files, then
runs Turbo's affected build, type-check, test, and browser graph. This preserves
workspace policy for new edits without expanding ^lint and ^check across
unrelated legacy debt. Repository-wide lint remains owned by the static job and
covers managed files excluded by workspace policy. An all-files-ignored result
is recorded explicitly as excluded; other tool failures remain fatal. Prove the
workflow contract locally, then
require a green pull-request Behavior contract before merging the repair. Keep
executed-task/test evidence, typed environment failures, manual-observation
state, and provider enforcement as subsequent slices rather than silently
expanding this patch.