fix(ci): externalize hardcoded Project + IM group IDs (W-05/W-06/W-07)#53
Conversation
W-05: auto-add-to-project.yml - Add project-number and org-login inputs (matching check-sprint pattern) - Dynamically resolve PROJECT_ID, Module field ID, and Sprint field ID via GraphQL at runtime instead of hardcoding Node IDs - REPO_MODULE now maps repo→module-name instead of repo→option-ID, resolving option IDs at runtime from the project field options - Project rebuild/field recreate now self-heals as long as names match W-06: octo-ci-status.yml - Add ci_status_group_id input with current value as default - Replace hardcoded group ID literal in Python script W-07: octo-issue-feed.yml - Add feed_group_id input with current value as default - Replace hardcoded group ID literal in Python script All three changes are backward-compatible: defaults match current behavior, no caller changes required.
lml2468
left a comment
There was a problem hiding this comment.
Review — PR #53 (.github) @ 829fd70
Verdict: APPROVED
Significant quality improvement across 3 reusable workflows — externalizes all hardcoded Project V2 + IM group IDs.
1. auto-add-to-project.yml (+98 -27)
Architecture upgrade: replaces 3 hardcoded Project V2 IDs (PROJECT_ID, MODULE_FIELD_ID, SPRINT_FIELD) + 19 hardcoded Module option IDs with runtime GraphQL resolution.
- New inputs:
project-number(default 2),org-login(default "Mininglamp-OSS") — backward compatible REPO_MODULEnow maps to human-readable names ('infra','adapters', etc.) instead of opaque option IDs — self-healing across project rebuilds- Runtime resolution via two GraphQL queries (Module field + Sprint field) with proper null checks and warning messages
- Error handling includes available options in warning for easy debugging
2. octo-ci-status.yml (+8 -1)
- Externalized hardcoded CI status group
'4ade985d984e432eb7fbdd0ad4f8118a'to inputci_status_group_idwith default - Uses
require_group_idfor hex format validation ✓
3. octo-issue-feed.yml (+8 -2)
- Externalized hardcoded issue feed group
'151a45970e1546afa9e947ac36a5c4e5'to inputfeed_group_idwith default - Uses
require_envfor non-empty check
Non-blocking
🟡 octo-issue-feed.yml:156 — require_env('FEED_GROUP_ID') checks non-empty but doesn't validate 32-char hex format. octo-ci-status.yml uses require_group_id for the same purpose. Consider adding require_group_id to issue-feed for parity (would need to define the function since it doesn't exist in that file).
CI all green. No blocking issues.
Jerry-Xin
left a comment
There was a problem hiding this comment.
This PR is in scope for Mininglamp-OSS/.github; it updates reusable workflows owned by this repository. I found no blocking issues.
💬 Non-blocking
- 🟡 Warning: octo-issue-feed.yml reads
FEED_GROUP_IDwithrequire_env()only. For consistency with octo-ci-status.yml andocto-pr-result-notify.yml, consider validating it as a 32-char lowercase hex group ID before sending. This is not blocking because send failures still fail the job and callers already control the supplied secret/input context.
✅ Highlights
- Dynamic Project V2 ID, field ID, and option ID resolution in auto-add-to-project.yml removes brittle Node ID coupling while preserving existing defaults.
- The privileged
pull_request_targetworkflow still avoids checkout or execution of PR code. octo-ci-status.ymlcorrectly validates the externalized CI group ID before use.
yujiawei
left a comment
There was a problem hiding this comment.
Code Review — PR #53 (.github)
Verdict: APPROVED
This is a clean, well-motivated change. It removes opaque hardcoded Project Node IDs / IM group IDs from three reusable workflows and replaces them with runtime name-based resolution (W-05) and externalized inputs with prod-matching defaults (W-06/W-07). I reviewed correctness, security/consistency, and behavioral equivalence. No P0/P1 issues; the items below are P2 nits and notes that do not block merge.
What I verified ✅
- Backward compatibility holds. All three new inputs are
required: falsewith defaults equal to current production values, so existing 19 callers need zero changes. Omitting the inputs reproduces the previous behavior exactly (orgs/Mininglamp-OSS/projects/2, group IDs4ade985d…/151a4597…). - REPO_MODULE name migration is faithful. I checked all 19 repos: every new module option name equals the prior
// commentfor its old opaque option ID, and the partition is preserved — repos that shared an option ID still share a name (e.g.infra← {.github, community, octo-version-sync};adapters← {claw-channel-octo, octo-adapters, openclaw-channel-octo};cli← {octo-cli, octo-daemon-cli};server← {octo-im, octo-server}). No silent split or merge. - The
|| defaultfallbacks are correct and load-bearing — not redundant.auto-add-to-project.ymlfires on three triggers (workflow_call,issues,pull_request_target). Theinputscontext is only populated on theworkflow_callpath; on theissues/pull_request_targetdirect-trigger pathsinputs.org-login/inputs.project-numberare empty, so${{ inputs.org-login || 'Mininglamp-OSS' }}/${{ inputs.project-number || 2 }}are required for those runs to resolve a valid project URL. Do not remove them — doing so would break the direct-trigger path (orgs//projects/). pull_request_targetsafety preserved. Noactions/checkoutof PR head is added,permissions: {}at workflow level, token only via thePROJECT_TOKENsecret. The inline SECURITY comment correctly warns against adding a checkout. Good.- GraphQL null-handling is sound. Each resolution step guards
!project?.idand!field?.idwithcore.warning(...) + return; the inline fragment on the wrong field type collapses to a null id and is caught. The not-found option path logs available option names — better diagnostics than the old opaque-ID failure.
Findings (non-blocking)
P2 — W-07 feed_group_id is not format-validated, unlike its W-06 sibling
octo-issue-feed.yml reads the new group id via feed_gid = require_env('FEED_GROUP_ID') (non-empty check only) and does not define require_group_id. octo-ci-status.yml validates the equivalent input with require_group_id → re.fullmatch(r'[0-9a-f]{32}', val) else sys.exit(2). Both inputs were externalized in this same PR with identically-shaped 32-hex defaults, so the validation contract should match.
This is a consistency / defense-in-depth gap, not a security hole: the group id is placed in the JSON request body as channel_id (json-escaped — no injection), not in the URL, and the request host is still pinned by the API_BASE_URL allowlist. Worst case from a malformed override is a failed IM send against the allowlisted endpoint — no SSRF/exfil. The shipped default is valid, so the happy path is unaffected.
Suggestion: port require_group_id into octo-issue-feed.yml (or factor a shared snippet) and use it for FEED_GROUP_ID so all three workflows share one validation contract.
P2 (note) — project-number numeric falsy edge
project-number is type: number; in ${{ inputs.project-number || 2 }} an explicitly-passed 0 is falsy and coerces to 2. Practical blast radius is nil (0 is not a valid Project number), so no action required — flagging only because the || idiom masks an invalid 0 rather than surfacing it. If stricter behavior is ever wanted, validate numerically in-script instead of via ||.
Note — resolution failure mode shifts (accepted design)
By moving from opaque IDs to name lookup, the dependency surface shifts: a future rename of a "Module"/"Sprint" option label in the Project UI would make options.find(o => o.name === …) miss and — under the pre-existing continue-on-error: true — skip the assignment while the workflow stays green. This is the intended, reasonable trade (human-readable, survives ID churn) and is already documented in the new code comments. Worth making board admins aware that option labels are now load-bearing. The continue-on-error: true is pre-existing (not introduced here), so this is not a regression.
Summary
Solid IaC hygiene improvement with the self-healing runtime resolution as a genuine robustness win on the ID-churn axis. The only thing I'd nudge before/after merge is aligning W-07's group-id validation with W-06's. Approving.
Summary
Eliminates all hardcoded GitHub Projects Node IDs and Octo IM group IDs from three reusable workflows.
W-05:
auto-add-to-project.yml— Dynamic field resolutionBefore:
PROJECT_ID,MODULE_FIELD_ID,SPRINT_FIELDwere hardcoded as opaque Node IDs.REPO_MODULEmapped repos to hardcoded option IDs. Combined withcontinue-on-error: true, a Project rebuild would silently break all Module + Sprint assignments with zero visibility.After:
project-number(default:2) andorg-login(default:Mininglamp-OSS) inputs, matchingreusable-check-sprint.ymlpatternREPO_MODULEnow mapsrepo → module-option-name(e.g."octo-server": "server") instead of opaque IDscore.warning()calls surface errors in CI logs instead of silent failurecontinue-on-error: trueretained (non-critical ops should not block board addition)Self-healing: Project rebuild, field delete+recreate, or option recreate — as long as field/option names remain the same, zero manual intervention needed.
W-06:
octo-ci-status.yml— Externalize CI status group IDci_status_group_idinput with current value as default"4ade985d..."literal in Python script withCI_STATUS_GROUP_IDenv varW-07:
octo-issue-feed.yml— Externalize issue feed group IDfeed_group_idinput with current value as default"151a4597..."literal in Python script withFEED_GROUP_IDenv varBackward compatibility
All three changes use defaults matching current production values. No caller changes required. Callers that want to override can pass the new inputs explicitly.
Verification