Harness Intelligence Wiki
Research

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:

  1. the fast root lint gate ran after roughly 16 minutes of expensive work;
  2. workspace-targeted lint excluded the file that root lint later rejected;
  3. focused test selectors could succeed while selecting no tasks or no tests;
  4. scaffold adoption preview exercised a full isolated dependency and lint lifecycle during feature convergence;
  5. host resource failures had no typed environment classification;
  6. implementation, manual validation, hosted CI, merge, and release state were recorded by separate authorities that could disagree; and
  7. 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:

LaneScope
Provider authorityPull-request timing, branch protection, behavior and release workflows, merge-tree identity
Validation feedbackCI ordering, Turbo selection, lint ownership, zero-test selectors, candidate preview
Scaffold ownershipBaseline identity, managed receipts, project-generated ownership, legacy path heuristics
Execution healthProcess 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-config is at b5670d64.
  • 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:

RunDurationFirst failing surface
336310167392m 14sWiki build in the combined build/type/test/browser command
3363185783610m 14sScaffold/update behavior assertions and dependency-version conflicts
3364648905712mTwenty CLI behavioral failures around Effect prepare and receipt ownership
3370504849217m 47sOne remaining CLI behavioral portfolio assertion
3370971805616m 18sThree packages/config lint findings
3371089329517m 30sOne 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-460 throws spawnSync errors verbatim; tar is invoked at :948 and :1265.
  • scripts/behavior-contract/consumer-repositories.test.ts:1060-1087 invokes Bun.spawnSync(["tar", ...]) without a typed startup-error branch.
  • scripts/validate-cutover-repeat.mjs:491-519 rejects raw start errors or generic nonzero exits, while :703-711 omits error.code from 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-applicable
  1. 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.
  2. Define one canonical lint owner per file. Exclusions must not make the recommended changed-file command silently skip a file covered by root CI.
  3. Add executed-task and executed-test counts to verification evidence. Empty selection must be an explicit not-applicable decision, never implicit success.
  4. Expose one local command that is byte-for-byte equivalent to the hosted behavior graph, including root correctness checks and exact base/head semantics.
  5. 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.
  6. 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.
  7. Separate baseline availability, last-applied baseline, managed-file receipt, project edit, provider receipt, and generated-output drift in the model and CLI presentation.
  8. 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.
  9. 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.
  10. 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; main has 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

  1. Is live Codex App observation required for merge, or is it a separately acknowledged platform gate?
  2. Should host preflight fail closed for all heavy gates or only classify the result as environment-blocked?
  3. Should the root correctness config or workspace config own managed hook source, and how should the same command be exposed to agents?
  4. If the current provider plan cannot enforce a required aggregate, which external authority should own the procedural merge gate?
  5. Should resource health be a public operation failure variant or orthogonal execution metadata?
  6. 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.

On this page