Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .github/workflows/windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,17 @@ on:
- master
pull_request:
workflow_dispatch:
inputs:
probe_source_gzip_base64:
description: Optional gzip/base64 Rust test source (at most 24000 characters)
type: string
required: false
default: ""
probe_source_sha256:
description: SHA-256 of the decompressed UTF-8 source bytes
type: string
required: false
default: ""

permissions:
contents: read
Expand Down Expand Up @@ -41,6 +52,20 @@ jobs:
RUST_BACKTRACE: "1"
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- name: Validate and mask optional probe input
if: github.event_name == 'workflow_dispatch' && (inputs.probe_source_gzip_base64 != '' || inputs.probe_source_sha256 != '')
run: |
# Read the event file before putting the payload in a step environment:
# otherwise the runner would log the raw env value before it is masked.
$event = Get-Content -LiteralPath $env:GITHUB_EVENT_PATH -Raw | ConvertFrom-Json
$payload = $event.inputs.probe_source_gzip_base64
$digest = $event.inputs.probe_source_sha256
if ([string]::IsNullOrEmpty($payload) -or $payload.Length -gt 24000) { throw 'Probe payload must contain 1..24000 characters' }
if ($payload -cnotmatch '\A[A-Za-z0-9+/]+={0,2}\z' -or $payload.Length % 4 -ne 0) { throw 'Probe payload must be single-line base64' }
if ($digest -cnotmatch '\A[0-9a-fA-F]{64}\z') { throw 'Probe requires a SHA-256 digest' }
Write-Output "::add-mask::$payload"
- name: Enable long dependency paths
run: git config --global core.longpaths true
- uses: mlugg/setup-zig@8d6198c65fb0feaa111df26e6b467fea8345e46f # v2.0.5
Expand Down Expand Up @@ -89,6 +114,7 @@ jobs:
# The native Ghostty build is now an output of the workspace crate.
cache-workspace-crates: true
cache-on-failure: true
save-if: ${{ github.event_name != 'workflow_dispatch' || (inputs.probe_source_gzip_base64 == '' && inputs.probe_source_sha256 == '') }}
- name: Check formatting
run: cargo fmt --check
- name: Check Clippy
Expand Down Expand Up @@ -141,6 +167,13 @@ jobs:
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
Write-Output $output
if ($output -ne "opencode-pty $version (protocol 7)") { throw "Unexpected executable version: $output" }
- name: Run optional ephemeral probe
if: github.event_name == 'workflow_dispatch' && inputs.probe_source_gzip_base64 != ''
timeout-minutes: 5
env:
OPENCODE_PTY_PROBE_GZIP_BASE64: ${{ inputs.probe_source_gzip_base64 }}
OPENCODE_PTY_PROBE_SHA256: ${{ inputs.probe_source_sha256 }}
run: ./script/windows-probe.ps1
- name: Document current coverage
if: always()
run: |
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
/target
/tests/windows-probe.rs
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,20 @@ gh run list --repo anomalyco/opencode-pty --workflow windows.yml --branch window
gh run view RUN_ID --repo anomalyco/opencode-pty --log-failed
```

Manual Windows dispatches can optionally supply `probe_source_gzip_base64` and
`probe_source_sha256` for a temporary diagnostic test. Ordinary PR/push runs
never execute this path. The hook accepts at most 24,000 encoded characters and
256 KiB of decompressed, strict UTF-8 source, verifies its SHA-256, and creates
only the reserved untracked `tests/windows-probe.rs` path (refusing an existing
entry). After the normal committed tests, it runs that target with a five-minute
step limit and removes exactly its generated source file in `finally`. Only this
optional target runs with `--test-threads=1` to isolate process-wide measurements;
individual probes may still exercise deliberate concurrency.
The payload is masked before it enters the step environment; the hook logs its
digest, target architecture, and results, not its source. Checkout credentials
are not persisted, workflow permissions stay read-only, and diagnostic runs do
not save Rust build caches or publish/upload their source or packages.

## Releases

Pushing a `vX.Y.Z` tag matching the version in `Cargo.toml` creates an unsigned
Expand Down
73 changes: 73 additions & 0 deletions script/windows-probe.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Manual-only diagnostic delivery. No payload is stored in Git or uploaded.
$ErrorActionPreference = 'Stop'
Set-StrictMode -Version Latest

$payload = $env:OPENCODE_PTY_PROBE_GZIP_BASE64
$expected = $env:OPENCODE_PTY_PROBE_SHA256
if ([string]::IsNullOrEmpty($payload) -or $payload.Length -gt 24000) {
throw 'Probe payload must contain 1..24000 characters'
}
if ($payload -cnotmatch '\A[A-Za-z0-9+/]+={0,2}\z' -or $payload.Length % 4 -ne 0) {
throw 'Probe payload must be single-line base64'
}
if ($expected -cnotmatch '\A[0-9a-fA-F]{64}\z') {
throw 'Probe requires a SHA-256 digest'
}

$path = Join-Path (Split-Path -Parent $PSScriptRoot) 'tests/windows-probe.rs'
if (Test-Path -LiteralPath $path) {
throw 'Reserved ephemeral probe path already exists'
}

$compressed = [Convert]::FromBase64String($payload)
$inputStream = [IO.MemoryStream]::new($compressed)
$gzip = [IO.Compression.GZipStream]::new($inputStream, [IO.Compression.CompressionMode]::Decompress)
$decoded = [IO.MemoryStream]::new()
try {
$buffer = [byte[]]::new(8192)
while (($count = $gzip.Read($buffer, 0, $buffer.Length)) -gt 0) {
if ($decoded.Length + $count -gt 262144) {
throw 'Decompressed probe exceeds 256 KiB'
}
$decoded.Write($buffer, 0, $count)
}
$source = $decoded.ToArray()
}
finally {
$decoded.Dispose()
$gzip.Dispose()
$inputStream.Dispose()
}

# Validate strict UTF-8 without re-encoding: the verified bytes are written as-is.
[void] [Text.UTF8Encoding]::new($false, $true).GetString($source)
$digest = [Convert]::ToHexString([Security.Cryptography.SHA256]::HashData($source)).ToLowerInvariant()
if ($digest -cne $expected.ToLowerInvariant()) {
throw 'Probe source SHA-256 mismatch'
}

$created = $false
try {
# CreateNew also protects against an existing entry appearing after Test-Path.
$file = [IO.File]::Open($path, [IO.FileMode]::CreateNew, [IO.FileAccess]::Write, [IO.FileShare]::None)
$created = $true
try {
$file.Write($source, 0, $source.Length)
}
finally {
$file.Dispose()
}
$env:OPENCODE_PTY_PROBE_GZIP_BASE64 = $null
$env:OPENCODE_PTY_PROBE_SHA256 = $null
Write-Output "Ephemeral probe SHA256=$digest target=$env:CARGO_BUILD_TARGET"
# Short compiler diagnostics avoid dumping source snippets into the job log.
& cargo test --locked --all-features --test windows-probe --message-format=short -- --test-threads=1 --nocapture
if ($LASTEXITCODE -ne 0) {
throw "Ephemeral probe failed with exit code $LASTEXITCODE"
}
}
finally {
if ($created) {
Remove-Item -LiteralPath $path -Force
}
}
Loading
Loading