fix(ci): harden workflow output handling #774
No reviewers
Labels
No labels
W6d-automerge-calibration
agent/claude-code
agent/codex
agent/hermes
agent/iskra
agent/ollama
agent/patchwarden
automerge-candidate
class/security-sensitive
cutover-gate
dependency/blocked
dependency/blocks-others
dependency/cross-repo
dependency/needs-confirmation
domain:agents
domain:ci
domain:docs
domain:forgejo
domain:infra
domain:memory
domain:runtime
domain:signal
domain:ux
flow/architecture
flow/blocked
flow/deployed
flow/done
flow/implementation
flow/intake
flow/maintained
flow/observed
flow/ready
flow/refining
flow/retired
flow/review
iterating
judge/codex-candidate
judge/hermes-candidate
judge/low-confidence
judge/needs-refinement
judge/operator-needed
judge/p0
judge/p1
judge/p2
judge/p3
judge/park
judge/patchwarden-candidate
judge/stale-priority
kind/adr
kind/bug
kind/chore
kind/feature
kind/infra
kind/ops
kind/refactor
kind/research
large-impact
merge/auto
merge/manual
merge/manual-dependency-conflict
merge/manual-failing-tests
merge/manual-merge-conflict
merge/manual-missing-review
merge/manual-operator-preference
merge/manual-red-zone
merge/manual-security-sensitive
merge/manual-unclear-scope
merge/manual-unknown
meta
mode:operator-only
mode:patchwarden-iskra-approved
mode:safe-auto
needs-operator-decision
needs-triage
not-ready
observed/erroring
observed/needs-followup
observed/pending
observed/retire-candidate
observed/unused
observed/used
operator-emotional
owner-attention
phase/02
phase/03
priority:p0
priority:p1
priority:p2
priority:p3
proposed
ready-for-agent
ready-for-operator
recovery
review:claude-reviewed
review:codex-reviewed
review:dziadek-reviewed
review:needs-human
risk/exposure
risk/process
risk/product
risk/runtime
safety:external-write
safety:no-prod-mutation
safety:prod-impact
safety:secret-touch
size/large
size/medium
size/small
size/tiny
size/unknown
source/adr
source/agent-generated
source/manual
source/operator-chat
source/voice-note
status:blocked
status:codex-ready
status:merged:pending-evidence
status:needs-evidence
status:operator-needed
status:parked
tier/full
tier/lite
tier/stacked
tier:0-platform-substrate
tier:1-iskra-value-layer
tier:2-tools-products-modules
type:bug
type:chore
type:docs
type:feat
type:policy
type:research
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
pdurlej/platform!774
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "codex/issues/729-ci-pipefail-output-guard"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Canary status: missing — fire canary 3+3 manually before merge
Canary Context Pack
Product story
Forgejo workflow outputs and deploy-host shell blocks should behave predictably on Forgejo runners. This keeps CI/deploy trust-boundary work from silently masking pipe failures or writing step outputs through a GitHub-only path.
What changed
set -euo pipefailto deploy-host run blocks inplatformctl-auto-apply.ymlandrelease-root-promote.yml, including the manual exit-code block whose commands are already guarded byif ...; then.$GITHUB_OUTPUTwrites with${FORGEJO_OUTPUT:-$GITHUB_OUTPUT}in the four affected workflows.$GITHUB_OUTPUToutput redirections, with coverage for quoted, unquoted, and braced variants.Why it changed
Issue #729 identified two related CI footguns: masked pipeline failures on trusted deploy-host jobs and GitHub-specific output paths in Forgejo workflows.
Files touched
.forgejo/workflows/platformctl-auto-apply.yml.forgejo/workflows/release-root-promote.yml.forgejo/workflows/base-is-main.yml.forgejo/workflows/w8-uptime-kuma-runtime-pilot.yml.forgejo/workflows/merged-in-main-audit.ymlcontrol-plane/platformctl/ci/lint_workflows.pycontrol-plane/platformctl/tests/test_forgejo_workflow_lint_contract.pyRelevant context
contexts/security-policy/MAP.md: workflow/deploy-runner trust-boundary changes route asclass/security-sensitiveandtier/full.errexit. The final diff keeps the captured commands insideif ...; thenconstructs.Runtime evidence
No runtime mutation. No deploy, no runner restart, no secret reads in repo output, no workflow dispatch.
Known constraints
$GITHUB_OUTPUT.Explicit out-of-scope
$GITHUB_ENV/$GITHUB_PATHbehavior.Requested decision
Run full canary 3+3 and merge if the workflow lint/test evidence holds.
Merge blockers
Plan apply healthcan bypass its manual exit-code output before exiting.$GITHUB_OUTPUTwrite in.forgejo/workflows.Spec sources read
contexts/security-policy/MAP.md: security-sensitive routing for workflow/deploy-runner trust-boundary changes..forgejo/workflows/platformctl-auto-apply.yml: target deploy-host and output writes..forgejo/workflows/release-root-promote.yml: target deploy-host shell block..forgejo/workflows/base-is-main.yml: target output writes..forgejo/workflows/w8-uptime-kuma-runtime-pilot.yml: target output writes..forgejo/workflows/merged-in-main-audit.yml: target output writes.control-plane/platformctl/ci/lint_workflows.py: lint insertion point.control-plane/platformctl/tests/test_forgejo_workflow_lint_contract.py: lint contract tests.Verification
PYTHONPATH=control-plane python3 -m pytest control-plane/platformctl/tests/test_forgejo_workflow_lint_contract.py-> 7 passed.PYTHONPATH=control-plane python3 control-plane/platformctl/ci/lint_workflows.py --markdown /tmp/729-workflow-lint.md --json /tmp/729-workflow-lint.json-> 0 findings.git diff --check-> passed.rg -n --pcre2 '(?<!FORGEJO_OUTPUT:-)\$GITHUB_OUTPUT|\$\{GITHUB_OUTPUT\}' .forgejo/workflows-> no matches.rg -n 'set -eu$|set -u$' .forgejo/workflows/platformctl-auto-apply.yml .forgejo/workflows/release-root-promote.yml-> no matches.Closes #729
Patchwarden PR sanity
advisory_findings77433e928231652c583079a855880b63cd37e64dedepresentDeterministic findings
infosensitive-path-touchedSensitive path touched —.forgejo/workflows/base-is-main.yml.forgejo/workflows/base-is-main.ymlinfosensitive-path-touchedSensitive path touched —.forgejo/workflows/merged-in-main-audit.yml.forgejo/workflows/merged-in-main-audit.ymlinfosensitive-path-touchedSensitive path touched —.forgejo/workflows/platformctl-auto-apply.yml.forgejo/workflows/platformctl-auto-apply.ymlinfosensitive-path-touchedSensitive path touched —.forgejo/workflows/release-root-promote.yml.forgejo/workflows/release-root-promote.ymlinfosensitive-path-touchedSensitive path touched —.forgejo/workflows/w8-uptime-kuma-runtime-pilot.yml.forgejo/workflows/w8-uptime-kuma-runtime-pilot.ymlModel reviewers
global-glm/glm-5.1:cloudStatus:
okVerdict:
OKmediumset -e in Plan apply health may bypass exit_code output on unexpected failuresIn .forgejo/workflows/platformctl-auto-apply.yml, the 'Plan apply health' step changes fromset -utoset -euo pipefail(line 176). This adds errexit to a step that writesexit_codeoutput before exiting. If any intermediate command fglobal-deepseek/deepseek-v4-pro:cloudokOKredteam/kimi-k2.6:cloudStatus:
okVerdict:
NOT_OKhighLinter bypass via single-angle redirect to bare GITHUB_OUTPUTcontrol-plane/platformctl/ci/lint_workflows.py introduces BARE_GITHUB_OUTPUT_REDIRECT_RE with patternr">>\s*['"]?(?:$GITHUB_OUTPUT\b|${GITHUB_OUTPUT})['"]?"; it requires>>, soecho foo > "$GITHUB_OUTPUT"orecho foo > $GITH`r">>?\s*['\"]?(?:\$GITHUB_OUTPUT\b|\$\{GITHUB_OUTPUT\})['\"]?"orr"(>|>>)\s*...", and add a contract test for the single->case.Policy notes
PLATFORMCTL_PR_SANITY_REDTEAM_MODELis configured.