2026-07-01 Dotfiles IPython Config Fixes and Claude Attribution Settings
Context: Cloud Claude Code session on dotfiles (github.com/achhina/dotfiles). Started as a one-line bug report about a broken IPython version guard and grew into two merged PRs (#1, #2) covering the guard fix, an IPython 9 theming migration, and Claude Code attribution settings for both local and cloud sessions.
What I set out to do
Fix the IPython version guard in nix/home-manager/modules/ipython.nix: it checked hasattr on c.TerminalInteractiveShell, which is a traitlets Config section, not the shell class — and config sections auto-create any attribute you touch, so the guard was unconditionally true.
What I actually did
- PR #1 — guard fix (merged). First pass imported the real
TerminalInteractiveShellclass and guarded withhasattron it. Then tightened to'auto_match' in set(_TIS.class_traits(config=True)), which checks the exact condition that matters: the trait exists and is settable via config. - PR #2 — three follow-ups (merged).
- Migrated
highlighting_style(deprecated no-op since IPython 9.0) to the IPython 9 theme system:c.InteractiveShell.colors = "gruvbox-dark"on 9+, old pygments style +colors = "Linux"on <9. - Replaced deprecated
includeCoAuthoredBy = falseinclaude.nixwith theattribution = { commit = ""; pr = ""; }object. - Added a repo-level
.claude/settings.jsonwithattribution(incl. undocumentedsessionUrl: false) so cloud sessions on this repo also skip attribution trailers.
- Migrated
- Commit authorship cleanup. The cloud container’s git identity was
Claude <noreply@anthropic.com>; rewrote the PR #1 commits asAdam Chhina <amanschhina@gmail.com>(byte-identical trees, trailers stripped) and set repo-local git config so later commits were mine from the start. - All changes verified against a real IPython 9.15.0 in a venv by rendering the full generated
ipython_config.pyand launchingipythonagainst it — clean startup, theme applied, guard working.
What was striking
- Traitlets
Config.__getattr__auto-vivifies: any unknown key returns a fresh emptyConfig, sohasattron a config section can never be false. The classic footgun the original guard fell into — while the ruff-formatter block right next to it got it right against the imported class. hasattron the class is still too loose. Names likesystem,mainloop,debugger_clspasshasattr(TerminalInteractiveShell, ...)but aren’t configurable traits — setting them viac.triggers “config option not recognized” warnings.class_traits(config=True)is the precise, self-documenting guard.- IPython 9 ships
gruvbox-darkas a built-in theme (IPython.utils.PyColorize.theme_table), so the deprecated pygments style mapped 1:1 with no visual downgrade. - Last write wins on a trait. The smoke test caught that the unconditional
c.InteractiveShell.colors = "Linux"later in the file silently overrode the theme — it had to move into theelsebranch. The bug first appeared in my test render, which was itself a reminder that verification scripts can have bugs too. - User
~/.claude/settings.jsondoes not carry over to Claude Code cloud containers — but a project-scoped.claude/settings.jsonin the repo does, since the clone is fresh. There’s no app-level setting. TheClaude-Session:URL trailer is a separate injection with only an undocumented toggle (attribution.sessionUrl, anthropics/claude-code#69614). - Force-push lease went stale after PR #1 merged because GitHub deleted the remote branch while the local tracking ref still pointed at it —
git remote prune originfixed it.
Top 3 next
- Run
hm switchlocally to apply theclaude.nixchange and confirm theattributionkey passes the module’s settings schema (couldn’t run nix in the cloud container). - Consider a SessionStart hook (or setup script) that sets git
user.name/user.emailin cloud containers so commits are authored correctly without post-hoc rewriting. - Replicate the
.claude/settings.jsonattribution block in other repos used with Claude Code on the web — it’s per-repo, not global.