Skip to content

fix(service): stage elevated Task Scheduler XML instead of inlining it (#4692) - #4759

Merged
lidge-jun merged 4 commits into
codex/win2-schtasks-gbkfrom
codex/win3-elevated-registration-length
Sep 16, 2026
Merged

lidge-jun merged 4 commits into
codex/win2-schtasks-gbkfrom
codex/win3-elevated-registration-length

Conversation

@lidge-jun

@lidge-jun lidge-jun commented Sep 16, 2026

Copy link
Copy Markdown
Owner

Summary

When ocx service repair re-registered the Task Scheduler task through the elevated fallback, the spawn failed before UAC ever appeared:

WindowsElevationError: ENAMETOOLONG: name too long, uv_spawn
    at startPowerShellCommand (src/lib/windows-elevation.ts:560)
    at runWindowsElevatedScheduledTaskRegistration (src/lib/windows-elevation.ts:704)
    at registerFreshWindowsSchedulerTask (src/service/windows-ops.ts:313)

runWindowsElevatedScheduledTaskRegistration embedded the new task XML and the expected-existing snapshot as base64(utf16le) inside an inner PowerShell script, which was then base64(utf16le)-encoded again into -EncodedCommand. Two base64 layers over UTF-16 cost roughly 14.2 command-line characters per XML character, and a replacement carries two payloads, so a ~2 KB definition put the outer command past the Windows limit. On a host where Task Scheduler exports the trigger scope as an account name (#4691) identityUpgradeNeeded stays true, so the re-register path runs on every repair and repair could never exit 0.

Both payloads are now staged to files, and the command carries two paths and two 64-character digests. Its length no longer depends on the size of the XML at all.

A file an administrator process will read is itself a privilege-escalation surface, so three properties hold together and none of them is sufficient alone:

  • Access. The staging directory is created fresh by mkdtemp and ACL-hardened through the existing hardenSecretDir/hardenSecretPath before anything is written into it, so the payload is private from the moment it exists.
  • No redirection. Each artifact is inspected with lstat and rejected unless it is what it claims to be. Exclusive wx creation inside a directory that did not exist a moment ago is the atomic step — there is no replace path to race — and the explicit check keeps that guarantee from resting on a reading of O_EXCL semantics.
  • Tamper evidence. The digest covers the exact bytes written, and the elevated script reads the file once, hashes what it read, and refuses before decoding. An ACL cannot cover this: a process running as the same user has the same SID and can rewrite the file, so the digest is what makes a swap during the UAC prompt fail closed instead of registering a different definition.

Cleanup runs on every exit — success, UAC cancellation, a synchronous spawn failure, a failed digest check, and a partial staging failure — and a cleanup error is aggregated with the registration error rather than replacing it.

The original "immutable bytes, never a caller-writable pathname" goal is kept by different means rather than abandoned, and the replacement precondition is untouched: the elevated process still re-queries the live registration and compares it to the verified predecessor before passing -Force. All three existing safety checks remain — capture-and-validate before elevation, re-check the predecessor immediately before UAC, and re-query and compare after consent.

Payloads are UTF-16LE with no BOM and are decoded straight into Register-ScheduledTask, so what is hashed is exactly what is registered, with no trimming step the two sides could disagree about.

This is the tip of a three-layer stack (#4746 -> #4749 -> this), so it targets codex/win2-schtasks-gbk. Retarget to dev once the parents land.

Closes #4692

Verification

No local test suite, single test file, typecheck, build or install was run — the repository owner prohibits it for this lane. Local verification is explicitly NOT RUN. The Windows job in this PR's hosted CI run is the only platform evidence for all three layers in the stack; the two parent layers carry [skip ci] by design, so this run gates the whole lane.

Static verification performed:

  • Traced the failing path: repairService -> reregisterWindowsSchedulerTask -> registerFreshWindowsSchedulerTask -> structured create/access-denied -> runWindowsElevatedScheduledTaskRegistration -> startPowerShellCommand, and confirmed the access-denied classification and the UAC-cancellation mapping are correct and unchanged; only the transport was wrong.
  • Derived the growth analytically before changing anything: inner = C + B(N) + r*B(M) and outer = 4*ceil(2*inner/3), giving about 14.22 * N for a replacement with equal-sized payloads, so a 2 KB definition contributes roughly 29,000 characters against the 32,767 limit. That is the arithmetic the size-independence test now pins.
  • Confirmed the two remaining production callers both hold an already-validated string and both route through the new stage/cleanup wrapper, and that the injected deps.elevate seam keeps its string signature so existing capture-before-elevation tests still exercise the same invariant.
  • Verified the elevated helper hashes and decodes the same byte array from a single ReadAllBytes, since hashing a path and reopening it would reintroduce the swap window.

Regression tests added or updated (they run in CI, not locally):

  • tests/windows/windows-elevation-spawn.test.ts asserts the elevated script carries the staged paths and digests, performs ReadAllBytes -> ComputeHash -> digest comparison before decoding, contains neither XML's base64 nor any FromBase64String, and that the outer command length is identical across payloads and stays under a conservative 8 KB bound for both the create and the replace shapes. It also pins that a replacement without a captured predecessor still throws.
  • tests/service/service.test.ts pins the staging contract: the directory is hardened before either payload is written, the digest equals a SHA-256 computed independently over the bytes actually on disk, the bytes are UTF-16LE with no BOM, cleanup is idempotent and removes everything, a redirected path is refused without hardening it, and a failure on the second payload still removes the first.

Not provable without a real Windows host, and not claimed: the exact quoting libuv produces before CreateProcessW and the precise length at which this Bun version reports ENAMETOOLONG; PowerShell 5.1 behaviour for SHA256/Unicode.GetString in this launcher chain; split-token UAC access to the current-user-hardened file on every account type; NTFS ACL and reparse behaviour on a real volume; and antivirus interference with cleanup timing.

One behavioural limitation worth calling out for review: hardenSecretPath grants the current user's SID and strips inheritance, so the staged files are readable by a split-token elevation of the same user (the normal case) but not by an elevation performed with a different administrator's credentials. The previous inline form had no such dependency. Widening the ACL to SYSTEM/Administrators would be a deliberate change to a security-sensitive module and is deliberately not bundled here.

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed. No user-facing documentation describes the elevated registration transport; the rationale lives in the doc comments on stageElevatedSchedulerRegistration and runWindowsElevatedScheduledTaskRegistration.
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults. This change adds a file that an elevated process reads, so it warrants explicit security review per MAINTAINERS.md. The staged payloads contain a task definition, not a credential; the directory is hardened before first write; paths are lstat-checked against redirection; the digest makes same-account tampering fail closed; cleanup covers every exit path; and the post-UAC compare-before--Force precondition is unchanged. No credential, token, OAuth, workflow or release-automation surface is touched, and nothing new is logged.

Prior-art check

No existing pull request, open or closed, implements this fix. Searched the repository's pull requests by issue number and by implementation signature, and inspected the adjacent antecedents (#3040, #4313, #2918, #3067) — each addresses a different problem and no code is carried from any of them. No Co-authored-by trailer is therefore owed. Recording the check here so the question does not have to be reopened.

Follow-up

The ACL limitation described above is tracked as #4779: whether the staged payloads should also grant SYSTEM and Administrators is a change to a security-sensitive module and is deliberately a separate decision. This PR makes the failure diagnosable rather than silent — the elevated read failure has its own protocol exit code and the parent reports the cause and the remedy instead of a bare number, so the limitation cannot present as an unexplained "access denied".

CI evidence (lane tip)

Hosted CI at the exact tip head 8a1b01011dbf68cd5eef07c568281590981fcaea:

  • Cross-platform CI, workflow_dispatch lane=all — success: run 35054231781. All 26 jobs succeeded, with no non-success conclusion. test 1/44/4 and windows 1/66/6 are all completed success.
  • The Windows suite is the reason for the dispatch: platform-windows is gated on github.event_name == 'workflow_dispatch', so it never runs on a pull_request event. Since all three layers of this lane are Windows-only, a PR-event run alone would carry no platform evidence for them.
  • enforce-target, PR hygiene and PR Labeler are all green at the same head.

An earlier pull_request run at the previous head 6a2b148f5a was also green (run 35051066482); the dispatch run above is a strict superset of its job set.

One earlier dispatch surfaced a windows 2/6 failure whose nine assertions were all in tests/clients/desktop-app-restart-posix.test.ts. That defect is not from this lane: the identical file and the identical nine line numbers appear on the dev-only dispatch 34795291889 from 2026-09-14. It is fixed on dev by f79c147309, which landed after this lane's original base, so dev was merged up the chain to absorb it. Verified by blob: this tree carried src/codex/desktop-app/windows.ts at 863a20057a before and c73e067e5b after.

#4692)

When "ocx service repair" re-registered the task through the elevated
fallback, the spawn failed before UAC ever appeared:

  WindowsElevationError: ENAMETOOLONG: name too long, uv_spawn
      at startPowerShellCommand (src/lib/windows-elevation.ts:560)
      at runWindowsElevatedScheduledTaskRegistration (.../windows-elevation.ts:704)

runWindowsElevatedScheduledTaskRegistration embedded the new task XML and the
expected-existing snapshot as base64(utf16le) inside an inner PowerShell
script, which was then base64(utf16le)-encoded again into -EncodedCommand. Two
base64 layers over UTF-16 cost roughly 14.2 command-line characters per XML
character, and a replacement carries two payloads, so a ~2 KB definition put
the outer command past the Windows limit. On a host where Task Scheduler
exports the trigger scope as an account name the re-register path runs on
every repair, so repair could never exit 0.

Both payloads are now staged to files and the command carries two paths and
two 64-character digests, so its length no longer depends on the size of the
XML at all.

A file an administrator process will read is itself a privilege-escalation
surface, so three properties hold together and none is sufficient alone:

- Access. The staging directory is created fresh by mkdtemp and ACL-hardened
  through the existing hardenSecretDir/hardenSecretPath before anything is
  written into it, so the payload is private from the moment it exists.
- No redirection. Each artifact is inspected with lstat and rejected unless it
  is what it claims to be. Exclusive "wx" creation inside a directory that did
  not exist a moment ago is the atomic step; the explicit check keeps that
  guarantee from resting on a reading of O_EXCL semantics.
- Tamper evidence. The digest covers the exact bytes written, and the elevated
  script reads the file once, hashes what it read, and refuses before decoding.
  An ACL cannot cover this: a process running as the same user has the same
  SID and can rewrite the file, so the digest is what makes a swap during the
  UAC prompt fail closed instead of registering a different definition.

Cleanup runs on every exit -- success, UAC cancellation, a synchronous spawn
failure, a failed digest check, and a partial staging failure -- and a cleanup
error is aggregated with the registration error rather than replacing it.

The original "immutable bytes, never a caller-writable pathname" goal is kept
by different means rather than abandoned, and the replacement precondition is
untouched: the elevated process still re-queries the live registration and
compares it to the verified predecessor before passing -Force.

Payloads are UTF-16LE with no BOM and are decoded straight into
Register-ScheduledTask, so what is hashed is exactly what is registered, with
no trimming step the two sides could disagree about.

Closes #4692
@lidge-jun
lidge-jun requested a review from Ingwannu as a code owner September 16, 2026 02:22
@coderabbitai

coderabbitai Bot commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (2)
  • ^dev$
  • ^preview$

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 97e65246-12f3-4278-a40b-2ed4604b727d

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 16, 2026

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 Completed 2026-09-16T02:25:49.467884Z ac87703 PR opened
ℹ️ 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 github-actions Bot added the bug Something isn't working label Sep 16, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Deterministic PR hygiene checks passed.

@lidge-jun

Copy link
Copy Markdown
Owner Author

리뷰 · 우선순위 77 / 80

이 PR은 Windows에서 ocx service repair가 elevated 폴백으로 Task Scheduler 작업을 다시 등록할 때, UAC가 뜨기도 전에 ENAMETOOLONG: name too long, uv_spawn으로 죽던 버그(#4692)를 고칩니다. 지금 dev HEAD(3070d64d8, 패키지 2.57.0)의 runWindowsElevatedScheduledTaskRegistration(src/lib/windows-elevation.ts)는 새 작업 XML과(교체 시) 기존 스냅샷 XML을 둘 다 base64(utf16le)로 안쪽 PowerShell에 넣고, 그 스크립트를 다시 base64(utf16le)로 -EncodedCommand에 실습니다. UTF-16 위에 base64가 두 겹이면 대략 XML 글자당 명령줄 14자 가까이 불어나고, 교체 경로는 페이로드가 두 개라 ~2KB 정의만으로도 Windows 한계(32767)를 넘기기 쉽습니다.

더 아픈 점은 #4691과 겹칩니다. Task Scheduler가 트리거 범위를 계정 이름으로 내보내는 호스트에서는 identityUpgradeNeeded가 수리마다 true라 재등록 경로가 매번 타고, 원래 작업이 관리자 소유면 비상승 schtasks /create /f는 Access denied → 결국 elevated만 남습니다. 그래서 수리(exit 0)가 영원히 안 됩니다. 이 PR은 페이로드를 명령줄에 넣지 않고, mkdtemp로 만든 스테이징 디렉터리에 UTF-16LE(BOM 없음) 파일로 쓴 뒤 경로+64자 SHA-256만 명령에 실어 명령줄 길이가 XML 크기와 무관해지게 만듭니다.

보안 모델도 같이 바뀝니다. 예전 목표는 “불변 바이트, 호출자가 쓰는 pathname은 안 넘김”이었고, 지금은 그 목표를 다른 수단으로 지킵니다. (1) 디렉터리를 먼저 hardenSecretDir/hardenSecretPath로 ACL 잠근 뒤 쓰고, (2) wx 생성 + lstat로 리다이렉트/심링크를 거절하고, (3) elevated 쪽이 ReadAllBytes 한 번으로 읽은 바이트를 해시·검증한 뒤에만 디코드합니다. 같은 사용자 SID면 ACL만으로는 UAC 대기 중 파일 교체를 막지 못하니, digest가 fail-closed의 핵심입니다. 교체 전제조건(라이브 등록 재조회 → 캡처된 predecessor와 비교 → 그다음에야 -Force)은 그대로입니다.

코드 경로는 stageElevatedSchedulerRegistration + runStagedElevatedSchedulerRegistration(src/service/windows-ops.ts)이 스테이징·정리·에러 합치기를 맡고, registerFreshWindowsSchedulerTask / restoreWindowsSchedulerTaskIfAbsent의 elevate seam이 그 래퍼로 바뀝니다. service.tsstageElevatedSchedulerRegistration을 re-export합니다. 테스트는 windows-elevation-spawn.test.ts에서 인라인 base64/FromBase64String 부재·digest-before-decode·명령 길이 불변(<8KB)·predecessor 없는 replace 거절을 고정하고, service.test.ts에서 harden-before-write·디스크 바이트 SHA-256·리다이렉트 거절·두 번째 페이로드 실패 시 첫 파일 정리를 고정합니다. 로컬 스위트/타입체크는 이 레인 금지이고, 부모 레이어가 [skip ci]인 스택이라 이 PR의 Windows 호스티드 CI가 세 층 전체의 플랫폼 증거가 됩니다.

현재 dev 대비 방향은 맞고, types/config 스플릿에 무효화되는 모놀리스 편집도 아닙니다. 다만 베이스가 dev가 아니라 codex/win2-schtasks-gbk(스택 #4746#4749 → 여기)이고, mergeable_state: unstable이며 Cross-platform/Service lifecycle Windows job이 아직 도는 중입니다. PR이 밝힌 ACL 한계(현재 사용자 SID만 열어 두면, 다른 관리자 자격으로 elevate하면 스테이징 파일을 못 읽을 수 있음)도 의도적 스코프 밖이지만 메인테이너가 한 번 짚을 만합니다.

라인 ~670+ (windows-elevation.ts READ_STAGED_TASK_XML) - 한 번 읽은 바이트로 해시→비교→디코드 순서가 맞고, 경로를 다시 여는 창을 닫았습니다. 경로·digest는 psSingleQuote로 들어가므로 일반적인 mkdtemp 경로에서는 안전해 보입니다.
경로 stageElevatedSchedulerRegistration (windows-ops.ts) - digest는 디스크를 다시 읽지 않고 메모리 bytes에 대해 계산합니다. 기본 writePayload는 그 바이트를 그대로 쓰므로 일치하고, 테스트도 온디스크 해시로 고정합니다. 주입 seam이 다른 바이트를 쓰면 elevated에서 fail-closed됩니다.
경로 hardenSecretPath ACL - 현재 사용자 SID + inheritance strip이라 split-token 같은 사용자 elevate는 되지만, 다른 관리자 자격 elevate는 스테이징을 못 읽을 수 있습니다. 예전 인라인 형태에는 없던 의존성입니다.
경로 base codex/win2-schtasks-gbk - dev 직접 머지 대상이 아닙니다. #4746·#4749가 먼저 안착해야 리타겟/머지 순서가 열립니다.
경로 Windows CI / Service lifecycle - 이 팁이 스택 전체의 플랫폼 게이트입니다. 그린 전에는 머지 증거가 부족합니다.

메인테이너의 판단이 필요한 지점

너의 추천
열어 두고 부모 #4746·#4749와 이 PR의 호스티드 Windows CI 그린을 기다린 뒤 스택 순서 머지(또는 부모 안착 후 dev 리타겟)하세요. 인라인 base64로 되돌리거나 교체 precondition을 느슨하게 만들지 마세요. Closes #4692는 머지 시 함께 닫히면 됩니다. types/config 스플릿 close-dont-rebase 대상 아닙니다.

이 댓글은 grok-bot이 작성했습니다

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ac87703657

ℹ️ 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".

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

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +220 to +224
const hardenDir = deps.hardenDir ?? ((path: string) => { hardenSecretDir(path, { required: true }); });
const writePayload = deps.writePayload ?? ((path: string, bytes: Buffer) => {
writeFileSync(path, bytes, { flag: "wx", mode: 0o600 });
});
const hardenPath = deps.hardenPath ?? ((path: string) => { hardenSecretPath(path, { required: true }); });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve access when UAC uses another administrator

When OpenCodex runs under a standard Windows account and the RunAs credential prompt is completed with a separate administrator account, hardenSecretDir and hardenSecretPath grant access only to the original user's SID and remove inherited grants. The elevated PowerShell process therefore cannot traverse or read these staged files, so every access-denied install or repair fallback fails at ReadAllBytes even though the previous inline transport worked. Stage the payload with an ACL readable by the elevated administrator context, or use a transport that does not depend on the unelevated user's private ACL while retaining the digest check.

Useful? React with 👍 / 👎.

Comment on lines +262 to +263
writePayload(path, bytes);
files.push(path);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Track a payload before its write can fail partially

If writeFileSync creates or partially writes the file and then throws—for example because the volume fills or an I/O error occurs—the path is never added to files. The catch path consequently attempts to remove only the directory, gets ENOTEMPTY, and leaves the staged payload behind, contradicting the cleanup-on-every-exit contract. Arrange for a file created by a failed write to be tracked and removed without risking deletion of a pre-existing path.

Useful? React with 👍 / 👎.

Staging the elevated Task Scheduler XML introduces exactly one new failure of
its own: hardenSecretPath grants the staging account and strips inheritance,
so a split-token elevation of the same user reads the file while an elevation
answered with a DIFFERENT administrator's credentials does not. The inline
form had no such dependency.

The elevated process runs hidden, so nothing it writes survives and only the
exit code crosses back. That made the failure an unexplained non-zero status --
the same undiagnosable shape as the ENAMETOOLONG this change set removes.

The read failure now has its own protocol code, and the parent turns it into a
message that names both the cause and the way out: approve the prompt as the
signed-in user, or run again from a session already elevated as that user. The
code sits outside OCX_ELEVATED_PROTOCOL_CODES, which is the create-and-run
transaction's alphabet, and cannot collide with UAC cancellation.

Whether to widen the ACL to SYSTEM and Administrators is left as a separate
security decision rather than bundled here, because it changes a
security-sensitive module.
…ce suite (#4692)

The file-size ratchet failed: tests/service/service.test.ts has a committed
cap of 4106 lines and the new staging cover pushed it to 4245. The ratchet only
ever lowers baselines, so growing past a cap is the thing it exists to refuse,
not something to re-baseline around.

The cover moves to tests/windows/windows-elevation-spawn.test.ts, which is the
better home anyway: its subject is the elevated registration payload, which is
exactly what these tests exercise. That file has no cap and stays well under
the 2000-line threshold, and the service suite returns to its baseline
unchanged, so no new test file and no test-layout registration are needed.

Also replaces a logical-assignment shorthand in the staging cleanup with the
explicit form the surrounding code already uses. No behaviour change; folded in
here rather than spending a separate CI cycle on it.
Brings in the Windows desktop-restart fix f79c147 through the chain, so this
lane's Windows evidence measures this lane.

The previous dispatch at 6a2b148 had windows 2/6 fail with nine assertions,
every one of them in tests/clients/desktop-app-restart-posix.test.ts and none
touching anything this lane changes. The same nine failures, at the same line
numbers, are on the dev-only dispatch 34795291889 from 2026-09-14, which is what
identifies the defect as pre-existing rather than introduced here. The other
eleven shards were green: test 1/4 through 4/4 and windows 1, 3, 4, 5 and 6 of 6.

No [skip ci] here: this is the lane tip, and its run is the gate for all three
layers.
@lidge-jun

Copy link
Copy Markdown
Owner Author

Landing the Windows service and update recovery lane, top layer first. Elevated Task Scheduler re-registration no longer passes the XML on the command line, so the payload size stops deciding whether the spawn exceeds the limit. The staging directory is ACL-hardened before anything is written to it, and the elevated script verifies the bytes it read by digest rather than reopening a path.

Evidence at the verified tip 8a1b010 (tree 5fb70432366550aab0177be5255b5097379d7935), from dispatch run 35054231781:

  • Every check-run at this commit concluded success. There is no failing, cancelled or pending check outside the always-skipped matrix placeholders.
  • test 1-4/4 and macos 1-2/2 all completed with conclusion success, confirmed through the check-runs API rather than the check rollup.
  • windows 1/6 through 6/6 all completed with conclusion success. That evidence is required rather than incidental here: every change in this lane is Windows-specific, and the platform-windows job is workflow_dispatch-only, so a pull_request run never exercises it.
  • The current head carries no pull_request run because the three branches were pushed together and the base and head of the tip moved almost simultaneously, so no synchronize event fired. The job sets were compared directly: the dispatch run covers 26 jobs against the pull-request run's 21, and no real job present in a pull-request run is absent from the dispatch. The only difference is a skipped matrix placeholder. The dispatch is a superset, at the exact head.
  • The lane absorbed dev at 5e3029e from the bottom layer upward, so each pull request keeps its own layer diff (13 / 2 / 4 files). Before that absorption, windows 2/6 failed on nine desktop-restart assertions that were live in dev and repaired by fix(codex): repair desktop restart membership and POSIX-only cases on Windows #4564; the same shard passes here.
  • git merge-tree --write-tree origin/dev <tip> reports a clean merge.
  • Ancestry verified so each layer closes as MERGED: win1-update-teardown and win2-schtasks-gbk are both ancestors of this tip.

Chained-child stacks merge top-down, so this lands in the parent branch and cascades to dev. CI evidence transfers by tree identity at each step.

Maintainer integration decision under MAINTAINERS.md / AGENTS.md: a maintainer with maintain or admin access may integrate into dev without a second maintainer approval, recording the decision and exact-head CI evidence.

@lidge-jun
lidge-jun merged commit 17d5c5e into codex/win2-schtasks-gbk Sep 16, 2026
35 checks passed
@lidge-jun
lidge-jun deleted the codex/win3-elevated-registration-length branch September 16, 2026 05:03
agentHits pushed a commit to agentHits/opencodex that referenced this pull request Sep 17, 2026
…registration-length

fix(service): stage elevated Task Scheduler XML instead of inlining it (lidge-jun#4692)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant