fix(review): add policy-file fallback so comment-only policy reaches cascade-tools#1483
Conversation
…cascade-tools The claude subprocess chain (@anthropic-ai/claude-code ≤ 2.1.185, bun-compiled) does not forward all custom env vars to bash subprocesses. CASCADE_REVIEW_EVENT_POLICY is only injected into the SDK env dict, never into the Docker container env — so it is uniquely vulnerable to this filtering (GITHUB_TOKEN and other credentials are also in the container env via buildWorkerEnvWithProjectId and survive). Fix: buildExecutionPlan writes the policy to /tmp/cascade-review-event-policy (inside the ephemeral worker container) before the agent starts. resolveEventPolicyFromEnv in cascade-tools scm create-pr-review reads this file as a fallback when the env var is absent. Both mechanisms remain active — whichever survives the subprocess chain wins. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ZaMpedeE8PXsix84cWsq1
…-1746) (#1480) Co-authored-by: Cascade Bot <bot@cascade.dev>
Co-authored-by: Cascade Bot <bot@cascade.dev>
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
…requests (#1482) Co-authored-by: Cascade Bot <bot@cascade.dev>
nhopeatall
left a comment
There was a problem hiding this comment.
Summary
Verdict: COMMENT (no blocking issues). This is a dev → main promotion bundling three independent changes — the review-event-policy file fallback (the titled fix), a forward-looking JIRA authType schema field (MNG-1736), and the self-directed review_requested exemption. All 48 affected unit tests pass locally and tsc --noEmit is clean. The changes are sound; I have one maintainability gap worth addressing before the JIRA field is consumed, plus one optional hardening note.
Verified correct
- Self-directed review exemption — traced all four cases: self-directed persona (
sender === requested_reviewer) → dispatch; cross-persona → skip (loop prevention preserved); non-persona self-request → skip with the not a cascade persona reason; human→persona → dispatch. The loop-safety claim holds — I greppedsrc/forrequestReviewers/request_reviewers/createReviewRequestand there are zero call sites, so everyreview_requestedevent is human-initiated by construction. The release-before-claim dedup atreview-requested.ts:147is pre-existing and correctly re-exercised by the new self-request path. Docs (CLAUDE.md,03-trigger-system.md) match the code. - Policy-file fallback — precedence is correct (env → file →
alldefault); the file is written only forcomment-only, and the CLI defaults toallwhen the file is absent, so the two halves are consistent.
Code Issues
Should Fix
src/pm/config.ts/ JIRAauthTyperuntime plumbing gap — the field is added tojiraConfigSchema,JiraConfig,JiraCredentials, and the manifest fixture, but the runtime config-mapper drops it.buildJiraConfiginsrc/db/repositories/configMapper.ts:263hand-picks fields (noauthType), its localJiraIntegrationConfiginterface (line 24) andProjectConfigRaw['jira'](line 139) omit it, andmapProjectRowreturnsProjectConfigRawdirectly (line 421) without re-parsing throughProjectConfigSchema. Net effect:ProjectConfig.jira.authTypeloaded from the DB is alwaysundefined, regardless of what's persisted. The newProjectConfig-level authType flowtest only exercisesProjectConfigSchema.parse(...)— not the runtime mapper path — so it stays green while the real load path silently omits the field. Since the field is explicitly forward-looking ("later stories consume this"), nothing breaks today, but the future consumer will readundefined. Recommend threadingauthTypethroughconfigMappernow, or documenting the deferral. (configMapper.tsisn't in this diff — inline note left onsrc/pm/config.ts.)
Nitpicks (non-blocking)
src/backends/secretOrchestrator.ts— the policy file is written only forcomment-onlyand never cleaned up. Safe under the one-job-per-ephemeral-container guarantee, but writing the resolved policy unconditionally would make it self-correcting and remove the reliance on/tmpnever being reused. See inline note.tests/unit/cli/scm/create-pr-review.test.ts— the new tests write to the real fixed path/tmp/cascade-review-event-policy(unlike the sibling tests, which usetmpdir()+ a unique name). This is inherent to the hardcoded-path design (it must survive env-stripping, so it can't be per-run), and the tests clean up inbefore/afterEach, but two concurrentvitestinvocations on the same host would collide. Acceptable, just noting the smell.
🕵️ claude-code · claude-opus-4-8 · run details
| * `'basic'` = classic site-token mode; `'scoped'` = scoped gateway-token | ||
| * mode. Absent ⇒ treated as `'basic'`. Later stories consume this field. | ||
| */ | ||
| authType?: 'basic' | 'scoped'; |
There was a problem hiding this comment.
authType is added here (and to jiraConfigSchema, JiraCredentials, and the manifest fixture), but the runtime config-mapper drops it. buildJiraConfig in src/db/repositories/configMapper.ts:263 hand-picks fields and doesn't copy authType (its local JiraIntegrationConfig interface at line 24 and ProjectConfigRaw['jira'] at line 139 also omit it), and mapProjectRow returns ProjectConfigRaw directly (line 421) without re-parsing through ProjectConfigSchema. So ProjectConfig.jira.authType loaded from the DB is always undefined, regardless of what's persisted.
The new ProjectConfig-level authType flow test only exercises ProjectConfigSchema.parse(...), which isn't the runtime path — it passes green while the mapper silently omits the field. Since this is forward-looking ("later stories consume this"), nothing breaks today, but the later consumer will read undefined. Suggest adding authType to configMapper's JiraIntegrationConfig, ProjectConfigRaw['jira'], and buildJiraConfig now, or documenting the deferral so it isn't a surprise. Also note the src/jira/types.ts comment says it's "carried through withJiraCredentials" — no call site populates it yet, which is fine but slightly overstates current behavior.
There was a problem hiding this comment.
Fixed — threaded authType through the runtime config-mapper so it survives the DB-load path.
src/db/repositories/configMapper.ts now carries it in the local JiraIntegrationConfig interface, in ProjectConfigRaw['jira'], and — critically — copies it in buildJiraConfig, which previously hand-picked fields and dropped authType before jiraConfigSchema re-parsed via validateConfig. So ProjectConfig.jira.authType now round-trips from the DB instead of always loading as undefined.
Added two mapper-level regression tests in configMapper.test.ts (preserves jira authType through the mapper / leaves jira authType undefined when not configured) that exercise the real mapProjectRow path, not just ProjectConfigSchema.parse.
Also reworded the src/jira/types.ts authType JSDoc so it no longer overstates: the field is reserved on JiraCredentials but no call site populates it yet (JiraIntegration.withCredentials still builds { email, apiToken, baseUrl } only).
| // bash subprocesses; the file lives in the container's ephemeral /tmp.) | ||
| if (isCommentOnlyReview(resolveReviewEventPolicy(project, agentType))) { | ||
| try { | ||
| writeFileSync(REVIEW_EVENT_POLICY_FILE, 'comment-only', 'utf-8'); |
There was a problem hiding this comment.
Optional hardening: the file is written only for comment-only and never removed. Safe under one-job-per-ephemeral-container, but if /tmp is ever reused across runs (non-container execution or a future change), an all run would read a stale comment-only file and incorrectly downgrade the review. Writing the resolved policy unconditionally (both all and comment-only) makes the file self-correcting and removes the reliance on container ephemerality — the CLI's env>file>default precedence still holds.
There was a problem hiding this comment.
Done — the policy file is now written unconditionally with the run's resolved policy (both all and comment-only) instead of only for comment-only.
It's self-correcting: an all run overwrites any stale comment-only file rather than inheriting it, removing the reliance on /tmp never being reused. The CLI's env > file > default precedence in resolveEventPolicyFromEnv() is unchanged.
Added tests in secretOrchestrator.test.ts asserting the write for the default (all), comment-only, and non-review-agent-on-a-comment-only-project (self-correcting) cases — mocking only writeFileSync so no real /tmp I/O happens during the run.
…ing review-policy file Address review feedback on PR #1483: - Thread the optional JIRA `authType` through the runtime DB-load path (`configMapper` `JiraIntegrationConfig`, `ProjectConfigRaw['jira']`, and `buildJiraConfig`). `buildJiraConfig` hand-picks fields, so `authType` was dropped before `jiraConfigSchema` re-parses and `ProjectConfig.jira.authType` always loaded back as `undefined`. Add mapper-level regression tests. - Reword `JiraCredentials.authType` JSDoc so it no longer overstates behavior: the field is reserved on the credentials shape but no call site populates it yet. - Write the review-event-policy file UNCONDITIONALLY with the run's resolved policy (both `all` and `comment-only`) so it self-corrects instead of relying on `/tmp` never being reused. CLI env > file > default precedence unchanged. Add tests asserting the write; mock only `writeFileSync` to avoid real disk I/O. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Summary
CASCADE_REVIEW_EVENT_POLICYis injected into the Claude SDK env dict but filtered byclaude.exe(bun v2.1.185) before bash subprocesses see it, socascade-tools scm create-pr-reviewnever received thecomment-onlypolicyGITHUB_TOKENand other credentials survive because they're also in the Docker container env (viabuildWorkerEnvWithProjectId);CASCADE_REVIEW_EVENT_POLICYhas no such fallback/tmp/cascade-review-event-policybefore the agent starts;cascade-toolsreads this file as a fallback when the env var is absentChanges
src/config/reviewEventPolicy.ts— addREVIEW_EVENT_POLICY_FILEconstantsrc/backends/secretOrchestrator.ts— write policy file whencomment-onlybefore agent startssrc/cli/scm/create-pr-review.ts— read policy file as fallback inresolveEventPolicyFromEnv()tests/unit/cli/scm/create-pr-review.test.ts— 5 new tests covering file fallback resolutionTest plan
sha256:9ee8c767)COMMENTwith advisory preamble🤖 Generated with Claude Code
https://claude.ai/code/session_011ZaMpedeE8PXsix84cWsq1