Skip to content

Commit a3076ee

Browse files
ShlomoSteptclaude
andcommitted
Refactor _github_repo to thread-safe contextvars
The module-level _github_repo variable posed a thread-safety risk when processing multiple sessions concurrently. This refactors to use Python's contextvars module which provides thread-local storage. Changes: - Add contextvars import - Create _github_repo_var ContextVar with None default - Add get_github_repo() accessor function (thread-safe) - Add set_github_repo() setter function (thread-safe) - Keep _github_repo module variable for backward compatibility - Update all internal usages to use new functions - Update test to use new API The backward-compatible design ensures existing code that reads _github_repo directly continues to work. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 7cbfbba commit a3076ee

File tree

2 files changed

+56
-14
lines changed

2 files changed

+56
-14
lines changed

src/claude_code_transcripts/__init__.py

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Convert Claude Code session JSON to a clean mobile-friendly HTML page with pagination."""
22

3+
import contextvars
34
import json
45
import html
56
import os
@@ -234,9 +235,49 @@ def extract_text_from_content(content):
234235
return ""
235236

236237

237-
# Module-level variable for GitHub repo (set by generate_html)
238+
# Thread-safe context variable for GitHub repo (set by generate_html)
239+
# Using contextvars ensures thread-safety when processing multiple sessions concurrently
240+
_github_repo_var: contextvars.ContextVar[str | None] = contextvars.ContextVar(
241+
"_github_repo", default=None
242+
)
243+
244+
# Backward compatibility: module-level variable that tests may still access
245+
# This is deprecated - use get_github_repo() and set_github_repo() instead
238246
_github_repo = None
239247

248+
249+
def get_github_repo() -> str | None:
250+
"""Get the current GitHub repo from the thread-local context.
251+
252+
This is the thread-safe way to access the GitHub repo setting.
253+
Falls back to the module-level _github_repo for backward compatibility.
254+
255+
Returns:
256+
The GitHub repository in 'owner/repo' format, or None if not set.
257+
"""
258+
ctx_value = _github_repo_var.get()
259+
if ctx_value is not None:
260+
return ctx_value
261+
# Fallback for backward compatibility
262+
return _github_repo
263+
264+
265+
def set_github_repo(repo: str | None) -> contextvars.Token[str | None]:
266+
"""Set the GitHub repo in the thread-local context.
267+
268+
This is the thread-safe way to set the GitHub repo. Also updates
269+
the module-level _github_repo for backward compatibility.
270+
271+
Args:
272+
repo: The GitHub repository in 'owner/repo' format, or None.
273+
274+
Returns:
275+
A token that can be used to reset the value later.
276+
"""
277+
global _github_repo
278+
_github_repo = repo
279+
return _github_repo_var.set(repo)
280+
240281
# API constants
241282
API_BASE_URL = "https://api.anthropic.com/v1"
242283
ANTHROPIC_VERSION = "2023-06-01"
@@ -1058,7 +1099,9 @@ def render_content_block(block):
10581099
commit_hash = match.group(1)
10591100
commit_msg = match.group(2)
10601101
parts.append(
1061-
_macros.commit_card(commit_hash, commit_msg, _github_repo)
1102+
_macros.commit_card(
1103+
commit_hash, commit_msg, get_github_repo()
1104+
)
10621105
)
10631106
last_end = match.end()
10641107

@@ -2148,9 +2191,8 @@ def generate_html(json_path, output_dir, github_repo=None):
21482191
"Warning: Could not auto-detect GitHub repo. Commit links will be disabled."
21492192
)
21502193

2151-
# Set module-level variable for render functions
2152-
global _github_repo
2153-
_github_repo = github_repo
2194+
# Set thread-safe context variable for render functions
2195+
set_github_repo(github_repo)
21542196

21552197
conversations = []
21562198
current_conv = None
@@ -2304,7 +2346,7 @@ def generate_html(json_path, output_dir, github_repo=None):
23042346
# Add commits as separate timeline items
23052347
for commit_ts, commit_hash, commit_msg, page_num, conv_idx in all_commits:
23062348
item_html = _macros.index_commit(
2307-
commit_hash, commit_msg, commit_ts, _github_repo
2349+
commit_hash, commit_msg, commit_ts, get_github_repo()
23082350
)
23092351
timeline_items.append((commit_ts, "commit", item_html))
23102352

@@ -2650,9 +2692,8 @@ def generate_html_from_session_data(session_data, output_dir, github_repo=None):
26502692
if github_repo:
26512693
click.echo(f"Auto-detected GitHub repo: {github_repo}")
26522694

2653-
# Set module-level variable for render functions
2654-
global _github_repo
2655-
_github_repo = github_repo
2695+
# Set thread-safe context variable for render functions
2696+
set_github_repo(github_repo)
26562697

26572698
conversations = []
26582699
current_conv = None
@@ -2806,7 +2847,7 @@ def generate_html_from_session_data(session_data, output_dir, github_repo=None):
28062847
# Add commits as separate timeline items
28072848
for commit_ts, commit_hash, commit_msg, page_num, conv_idx in all_commits:
28082849
item_html = _macros.index_commit(
2809-
commit_hash, commit_msg, commit_ts, _github_repo
2850+
commit_hash, commit_msg, commit_ts, get_github_repo()
28102851
)
28112852
timeline_items.append((commit_ts, "commit", item_html))
28122853

tests/test_generate_html.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -526,11 +526,12 @@ def test_tool_result_with_ansi_codes(self):
526526

527527
def test_tool_result_with_commit(self, snapshot_html):
528528
"""Test tool result with git commit output."""
529-
# Need to set the global _github_repo for commit link rendering
529+
# Need to set the github repo for commit link rendering
530+
# Using the thread-safe set_github_repo function
530531
import claude_code_transcripts
531532

532-
old_repo = claude_code_transcripts._github_repo
533-
claude_code_transcripts._github_repo = "example/repo"
533+
old_repo = claude_code_transcripts.get_github_repo()
534+
claude_code_transcripts.set_github_repo("example/repo")
534535
try:
535536
block = {
536537
"type": "tool_result",
@@ -540,7 +541,7 @@ def test_tool_result_with_commit(self, snapshot_html):
540541
result = render_content_block(block)
541542
assert result == snapshot_html
542543
finally:
543-
claude_code_transcripts._github_repo = old_repo
544+
claude_code_transcripts.set_github_repo(old_repo)
544545

545546
def test_tool_result_with_image(self, snapshot_html):
546547
"""Test tool result containing image blocks in content array.

0 commit comments

Comments
 (0)