Summary
generate-status-panel crashes with AttributeError: 'dict' object has no attribute 'replace' in its HTML escape path, leaving a partial file written each invocation. This is distinct from cc-workflow#497 (which closed an AttributeError: 'NoneType' object has no attribute 'replace' after wave_complete) — that one was about NoneType, this one is about dict.
Severity: medium — operator visibility tool broken; partial file shipping is worse than no file.
Same script also still hits the NoneType variant (observed by orchestrator during Plan #607 wave-cleanup paths). Likely both errors share a root cause: .replace() is called on values that aren't always strings. @Scryer should consider whether to fix in #497's wake or here as a separate ticket — either way, both shapes need defensive handling at the escape layer.
Origin: Reported by sibling-campaign agent. @Scryer to expand on the exact codepath (most likely a wave/state field that was assumed string but is sometimes dict for cross-repo refs or during certain campaign phases).
Implementation Steps
TBD — @Scryer to expand. Probably needs an isinstance(value, str) guard or a str(value) coercion in the HTML escape function.
Test Procedures
TBD — should include both the dict and NoneType variants as regression cases.
Acceptance Criteria
Dependencies
- cc-workflow#497 (related — same family of AttributeError)
Metadata
Severity: severity::major (visibility tool reliability)
Origin: sibling-campaign tracker. @Scryer owns expansion.
Summary
generate-status-panelcrashes withAttributeError: 'dict' object has no attribute 'replace'in its HTML escape path, leaving a partial file written each invocation. This is distinct from cc-workflow#497 (which closed anAttributeError: 'NoneType' object has no attribute 'replace'after wave_complete) — that one was about NoneType, this one is about dict.Severity: medium — operator visibility tool broken; partial file shipping is worse than no file.
Same script also still hits the NoneType variant (observed by orchestrator during Plan #607 wave-cleanup paths). Likely both errors share a root cause:
.replace()is called on values that aren't always strings. @Scryer should consider whether to fix in #497's wake or here as a separate ticket — either way, both shapes need defensive handling at the escape layer.Origin: Reported by sibling-campaign agent. @Scryer to expand on the exact codepath (most likely a wave/state field that was assumed string but is sometimes dict for cross-repo refs or during certain campaign phases).
Implementation Steps
TBD — @Scryer to expand. Probably needs an
isinstance(value, str)guard or astr(value)coercion in the HTML escape function.Test Procedures
TBD — should include both the dict and NoneType variants as regression cases.
Acceptance Criteria
generate-status-paneldoes not crash on dict-valued fields in state.json/phases-waves.jsonDependencies
Metadata
Severity: severity::major (visibility tool reliability)
Origin: sibling-campaign tracker. @Scryer owns expansion.