B-016 landed in docs/roadmap.md with this provenance line:
source: bbpark | cwd: /Users/<username>/Development/BBBrain/.claude/commands
The brainstorm was real. The path was accurate. The problem was that the raw filesystem path would sit in the committed document permanently, attached to the idea, clearly identifying which machine had generated it. The idea park-sprint-4 had just merged. I was looking at what it had already written into four brainstorm entries before the provenance output had been checked.
That was the first of three fixes that all turned out to be the same fix.
What the idea park is supposed to do
The idea park is the idea-capture command for the editorial pipeline. Any session can call it to park a thought in the roadmap and backlog with a consistent audit trail. That includes a main Claude Code session, a subagent, or a launchd cron. The point is provenance: when the idea landed, which session or agent produced it, what the surrounding context was.
The audit trail only has value if it’s accurate. That sounds obvious. It took three post-merge fixes to make it true.
Fix one: the path that didn’t sanitise
_build_provenance generates the provenance line written into docs/roadmap.md for every brainstorm. Before Sprint 4’s merge, it took the raw cwd value from _build_context_hint without sanitisation.
_build_context_hint already had a _sanitise_cwd() helper. It had been written to strip real usernames from paths when generating context hints, the same leak-prevention concern applied earlier in the same file. _build_provenance wasn’t calling it.
The fix extracted _sanitise_cwd() into a shared helper, replacing /Users/<username>/... with ~/... in every provenance line. Straightforward once the problem was visible.
Four brainstorm entries landed via smoke runs before the fix. B-016 through B-019 had to be backfilled manually. Each had a raw path in the committed line. The backfill replaced them with ~/Development/BBBrain/....
Out of scope: auditing every other provenance field for similar leaks. The path was the only field writing a raw system value.
Fix two: the installer pointing at the wrong directory
The the idea park-soak cron installer was adapted from the the article pipeline pattern. In the article pipeline, the template plist sits one directory above the installer script. SCRIPT_DIR is the installer’s directory and the template is at $SCRIPT_DIR/... That reflects the article pipeline’s physical layout.
The idea park-soak co-locates the installer and the template in the same directory, matching the inbox-sync convention instead. The /.. that worked in the article pipeline resolved to the parent, which didn’t contain the template.
The fix was dropping the /... One path segment. The reason it slipped through: the the article pipeline installer worked fine and the idea park’s was modelled on it before the co-location decision was made. When you copy an installer pattern, check that the directory assumptions transfer.
Fix three: the misclassified caller
When a subagent calls the idea park, the audit entry records who called it. The mechanism is BB_CALLER, an environment variable the calling agent sets before invoking the idea park. If BB_CALLER isn’t set, the idea park falls back to claude_session.
The fallback is correct for interactive sessions. It’s not correct for agents. An agent that calls the idea park without setting BB_CALLER produces an entry attributed to claude_session, the same attribution every direct CJ session produces.
That matters beyond labelling. Context shapes how an entry should be read. An interactive parking carries session context that doesn’t make it into the brainstorm text. A subagent parking is a different signal entirely: a machine process noticed something worth capturing. If both show up as claude_session, you can’t tell them apart without cross-referencing timestamps against session history. The audit trail is supposed to make that unnecessary.
There were agents in ~/.claude/agents/*.md that declared the Bash tool without any mention of BB_CALLER in their documentation. Not necessarily broken in practice, but undocumented and drift-prone. One refactor and the call gets made without the convention being remembered.
lint_agents.py scans every .md file in ~/.claude/agents/ and warns on any agent that declares Bash in its tool list without mentioning BB_CALLER. It doesn’t enforce, it flags. Some agents legitimately don’t call the idea park at all; blocking on absence of the convention would produce false positives. The output is a warning list, not a failure.
The pattern
Three fixes, same category:
_build_provenancewriting raw paths instead of sanitised ones.- An installer with
/..pointing at a parent directory the co-located template doesn’t live in. - Agent documentation without the caller convention the audit trail depends on.
None of these broke the idea park in the sense that ideas stopped capturing. All three corrupted the trail in different ways: paths that identify a specific machine, silent cron setup failures on co-located installs, caller misclassification that obscures whether an idea came from a human or an agent.
The idea park’s purpose is the audit trail. A capture system with an inaccurate trail is worse than no trail. It looks authoritative while being wrong. Every one of these fixes was the audit trail being wrong.
What the order tells you
The smoke runs that generated B-016 through B-019 ran before the provenance sanitiser existed. They’re in the committed roadmap with the backfilled paths now. The fact that four entries landed before the sanitiser was written tells you something about how correctness and capture tend to ship in order.
The idea park was built sprint by sprint, each sprint proving the previous one worked before extending. That’s the right shape for building fast. But “it captures ideas” and “the trail is clean” are different tests. The first one is visible immediately. The second requires looking at what the trail actually records, not just that it’s non-empty.
lint_agents.py is the first tool that checks a property of the audit trail’s inputs rather than the trail itself. Phase F6 retires the soak cron after the lint has been running cleanly, an acknowledgement that correctness gates need to exist before the system is left unattended.
The fixes are shipped. The trail is clean on the paths that matter. Whether the same class of problem is hiding elsewhere in the idea park’s output is a separate audit.
