Skip to content

Commit 626c24b

Browse files
NagyViktNagyVikt
andauthored
Keep Claude edit tracking compatible with Python 3.10 (#566)
Claude Code invokes repo-local PostToolUse hooks through system python3, which can still be Python 3.10. The edit tracker imported datetime.UTC, so the hook failed before it could safely ignore or record edit payloads. Using timezone.utc keeps the same timestamp semantics without requiring Python 3.11. Constraint: Claude hook command uses python3 from the local shell environment Rejected: Pin the hook to a newer interpreter | Claude settings should not assume a repo-local Python runtime Confidence: high Scope-risk: narrow Directive: Keep .claude and .codex hook copies in parity for shared agent-hook behavior Tested: node --test test/post-edit-tracker-hook.test.js Tested: python3 -m py_compile .claude/hooks/post_edit_tracker.py .codex/hooks/post_edit_tracker.py Tested: openspec validate agent-codex-codex-task-2026-05-12-10-39 --type change --strict Tested: openspec validate --specs Co-authored-by: NagyVikt <nagy.viktordp@gmail.com>
1 parent 1a39ebe commit 626c24b

7 files changed

Lines changed: 87 additions & 4 deletions

File tree

.claude/hooks/post_edit_tracker.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import json
1010
import sys
11-
from datetime import UTC, datetime
11+
from datetime import datetime, timezone
1212
from pathlib import Path
1313

1414
try:
@@ -80,7 +80,7 @@ def main() -> None:
8080
files.append(file_path)
8181
state["files"] = files
8282
state["modified"] = True
83-
state["last_modified"] = datetime.now(UTC).isoformat()
83+
state["last_modified"] = datetime.now(timezone.utc).isoformat()
8484

8585
with open(state_path, "w") as f:
8686
json.dump(state, f, indent=2)

.codex/hooks/post_edit_tracker.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import json
1010
import sys
11-
from datetime import UTC, datetime
11+
from datetime import datetime, timezone
1212
from pathlib import Path
1313

1414
try:
@@ -80,7 +80,7 @@ def main() -> None:
8080
files.append(file_path)
8181
state["files"] = files
8282
state["modified"] = True
83-
state["last_modified"] = datetime.now(UTC).isoformat()
83+
state["last_modified"] = datetime.now(timezone.utc).isoformat()
8484

8585
with open(state_path, "w") as f:
8686
json.dump(state, f, indent=2)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
schema: spec-driven
2+
created: 2026-05-12
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
## Why
2+
3+
- Claude Code reports a non-blocking `PostToolUse` hook traceback after edits because the repo-local Python hook imports `datetime.UTC`, which is unavailable under Python 3.10.
4+
- The hook should run cleanly on the Python versions commonly exposed as `python3` in agent shells.
5+
6+
## What Changes
7+
8+
- Replace `datetime.UTC` with `datetime.timezone.utc` in the Claude and Codex edit-tracker hook copies.
9+
- Add focused regression coverage that executes the Claude `PostToolUse` edit tracker through system `python3`.
10+
11+
## Impact
12+
13+
- Affects only local agent hook bookkeeping for edited files.
14+
- No runtime package behavior changes outside hook compatibility.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
## ADDED Requirements
2+
3+
### Requirement: PostToolUse edit tracker Python compatibility
4+
The `PostToolUse` edit-tracker hook SHALL run successfully under the system `python3` used by Claude Code when that interpreter is Python 3.10 or newer.
5+
6+
#### Scenario: Claude edit tracker hook starts cleanly
7+
- **WHEN** Claude Code invokes `.claude/hooks/post_edit_tracker.py` with a valid `PostToolUse` payload
8+
- **THEN** the hook exits with status `0`
9+
- **AND** no `ImportError` traceback is emitted for `datetime.UTC`.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
## Definition of Done
2+
3+
This change is complete only when **all** of the following are true:
4+
5+
- Every checkbox below is checked.
6+
- The agent branch reaches `MERGED` state on `origin` and the PR URL + state are recorded in the completion handoff.
7+
- If any step blocks (test failure, conflict, ambiguous result), append a `BLOCKED:` line under section 4 explaining the blocker and **STOP**. Do not tick remaining cleanup boxes; do not silently skip the cleanup pipeline.
8+
9+
## Handoff
10+
11+
- Handoff: change=`agent-codex-codex-task-2026-05-12-10-39`; branch=`agent/<your-name>/<branch-slug>`; scope=`TODO`; action=`continue this sandbox or finish cleanup after a usage-limit/manual takeover`.
12+
- Copy prompt: Continue `agent-codex-codex-task-2026-05-12-10-39` on branch `agent/<your-name>/<branch-slug>`. Work inside the existing sandbox, review `openspec/changes/agent-codex-codex-task-2026-05-12-10-39/tasks.md`, continue from the current state instead of creating a new sandbox, and when the work is done run `gx branch finish --branch agent/<your-name>/<branch-slug> --base dev --via-pr --wait-for-merge --cleanup`.
13+
14+
## 1. Specification
15+
16+
- [x] 1.1 Finalize proposal scope and acceptance criteria for `agent-codex-codex-task-2026-05-12-10-39`.
17+
- [x] 1.2 Define normative requirements in `specs/codex-task/spec.md`.
18+
19+
## 2. Implementation
20+
21+
- [x] 2.1 Implement scoped behavior changes.
22+
- [x] 2.2 Add/update focused regression coverage.
23+
24+
## 3. Verification
25+
26+
- [x] 3.1 Run targeted project verification commands.
27+
- [x] 3.2 Run `openspec validate agent-codex-codex-task-2026-05-12-10-39 --type change --strict`.
28+
- [x] 3.3 Run `openspec validate --specs`.
29+
30+
## 4. Cleanup (mandatory; run before claiming completion)
31+
32+
- [ ] 4.1 Run the cleanup pipeline: `gx branch finish --branch agent/<your-name>/<branch-slug> --base dev --via-pr --wait-for-merge --cleanup`. This handles commit -> push -> PR create -> merge wait -> worktree prune in one invocation.
33+
- [ ] 4.2 Record the PR URL and final merge state (`MERGED`) in the completion handoff.
34+
- [ ] 4.3 Confirm the sandbox worktree is gone (`git worktree list` no longer shows the agent path; `git branch -a` shows no surviving local/remote refs for the branch).
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
const test = require('node:test');
2+
const assert = require('node:assert/strict');
3+
const cp = require('node:child_process');
4+
const path = require('node:path');
5+
6+
const repoRoot = path.resolve(__dirname, '..');
7+
8+
test('PostToolUse edit tracker runs on Python 3.10 compatible datetime APIs', () => {
9+
const payload = JSON.stringify({
10+
session_id: 'python-compat',
11+
cwd: repoRoot,
12+
tool_input: {
13+
file_path: path.join(repoRoot, 'README.md'),
14+
},
15+
});
16+
17+
const result = cp.spawnSync('python3', ['.claude/hooks/post_edit_tracker.py'], {
18+
cwd: repoRoot,
19+
input: payload,
20+
encoding: 'utf8',
21+
});
22+
23+
assert.equal(result.status, 0, result.stderr || result.stdout);
24+
});

0 commit comments

Comments
 (0)