Add Crow-Session attribution trailer to commits#249
Merged
dhilgaertner merged 1 commit intomainfrom May 7, 2026
Merged
Conversation
Wire setup.sh to write a per-worktree .claude/settings.local.json that overrides Claude Code's attribution.commit so commits include a Crow-Session: <uuid> trailer alongside the standard Co-Authored-By: Claude line. The session UUID is a stable handle back to crow session metadata via `crow get-session <uuid>`. A new top-level attributionTrailers flag on AppConfig (default true) toggles the behavior, exposed in Settings → Automation. setup.sh adds the local settings file to the per-worktree git exclude list so it never gets committed accidentally. Closes #246 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
dgershman
approved these changes
May 7, 2026
Collaborator
dgershman
left a comment
There was a problem hiding this comment.
Code & Security Review
Critical Issues
None blocking.
Security Review
Strengths:
- The
settings.local.jsonis excluded from version control via the per-worktree git exclude file, preventing accidental commits of session metadata. - The
is_attribution_trailers_enabledfunction defaults to on (return 0) when the config is missing or malformed, matchingAppConfig'sdecodeIfPresentdefault — good parity. - The
write_settings_localfunction gracefully handles missingSESSION_IDand disabled config without failing the overall setup.
Concerns:
setup.sh:349-354—SESSION_IDis interpolated directly into a heredoc without sanitization. When supplied via--session-idfrom the CLI, a malicious value could inject arbitrary keys into the JSON. In practice, the value comes fromcrow new-session(a trusted binary), and the--session-idpath is only used internally by the LLM orchestrator, so the risk is low. Still, a UUID format check (e.g.,[[ "$SESSION_ID" =~ ^[a-f0-9-]{36}$ ]]) before interpolation would be a cheap hardening measure.setup.sh:83-84— The grep-based config parsing (is_attribution_trailers_enabled) searches for a literal string pattern rather than parsing JSON. A contrived config with"attributionTrailers": falseinside a string value (e.g., a"note"field) would produce a false negative. Usingjqwould be more robust, but the current approach is consistent with the existingis_remote_control_enabledpattern and unlikely to cause issues with normal Crow-managed configs.
Code Quality
- AppConfig.swift: Clean addition following the established pattern —
decodeIfPresentwith a default,CodingKeysupdated, init parameter added. Consistent withexperimentalTmuxBackendwhich was the last addition. - Tests: Two new tests (
appConfigAttributionTrailersRoundTrip,appConfigAttributionTrailersDefaultsTrueWhenKeyMissing) cover the round-trip and legacy-config-upgrade paths. The empty-JSON decode test also asserts the new default. All 145 CrowCore tests pass. - UI wiring:
AutomationSettingsViewcorrectly takes anattributionTrailersbinding and saves on toggle.SettingsViewpasses it through. The help text ("Applies to new worktrees only") is accurate and sets user expectations correctly. - SKILL.md: Documentation is well-placed and includes opt-out instructions.
- setup.sh: The
write_settings_localfunction is well-structured — it runs for all worktrees (not just--primary), handles edge cases gracefully, and the git-exclude logic is idempotent (checksgrep -qxFbefore appending).
Summary Table
| Priority | Issue |
|---|---|
| 🟡 Yellow | Consider adding UUID format validation for SESSION_ID before JSON interpolation in write_settings_local (setup.sh:338-339) |
| 🟢 Green | Consider using jq for config flag parsing instead of grep (lower priority — consistent with existing is_remote_control_enabled pattern) |
Recommendation: Approve — clean, well-tested feature with good UX (default-on, easy opt-out, clear help text). The yellow item is a hardening suggestion for a future pass, not a blocker.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
setup.shwrites a per-worktree.claude/settings.local.jsonthat overrides Claude Code's nativeattribution.committo include aCrow-Session: <session-uuid>trailer alongside the standardCo-Authored-By: Claudeline. The session UUID is a stable handle back to session metadata viacrow get-session <uuid>.attributionTrailersflag onAppConfig(defaulttrue) toggles the behavior. Exposed in Settings → Automation → Attribution.setup.shalso appends.claude/settings.local.jsonto the per-worktree git exclude file so the override never gets committed accidentally, even when the repo's tracked.gitignoredoesn't already cover it.The implementation switched from the originally-proposed CLAUDE.md / prompt-injection approach to Claude Code's native
attribution.commitsetting after research — it's deterministic (no relying on the LLM to remember an instruction every commit) and survives prompt compaction by virtue of being a config file Claude Code re-reads.The trailer body chosen:
Test plan
make appbuilds cleanlyswift test --package-path Packages/CrowCore(145 tests, including 2 new ones)swift test --package-path Packages/CrowPersistence(24 tests)write_settings_localagainst a scratch git worktree:"attributionTrailers": falseskips the file"attributionTrailers" : false)git statusreports the file as untracked-and-excludedpython3 -c 'json.load(...)'and decodes to the expected three-line trailer bodygit log -1 --format='%B'shows bothCo-Authored-By: ClaudeandCrow-Session: <real-uuid>lines{devRoot}/.claude/config.jsonCloses #246