diff --git a/.claude/hooks/post_edit_tracker.py b/.claude/hooks/post_edit_tracker.py index 54d2918c..935cfbd0 100755 --- a/.claude/hooks/post_edit_tracker.py +++ b/.claude/hooks/post_edit_tracker.py @@ -8,7 +8,7 @@ import json import sys -from datetime import UTC, datetime +from datetime import datetime, timezone from pathlib import Path try: @@ -80,7 +80,7 @@ def main() -> None: files.append(file_path) state["files"] = files state["modified"] = True - state["last_modified"] = datetime.now(UTC).isoformat() + state["last_modified"] = datetime.now(timezone.utc).isoformat() with open(state_path, "w") as f: json.dump(state, f, indent=2) diff --git a/.codex/hooks/post_edit_tracker.py b/.codex/hooks/post_edit_tracker.py index 54d2918c..935cfbd0 100755 --- a/.codex/hooks/post_edit_tracker.py +++ b/.codex/hooks/post_edit_tracker.py @@ -8,7 +8,7 @@ import json import sys -from datetime import UTC, datetime +from datetime import datetime, timezone from pathlib import Path try: @@ -80,7 +80,7 @@ def main() -> None: files.append(file_path) state["files"] = files state["modified"] = True - state["last_modified"] = datetime.now(UTC).isoformat() + state["last_modified"] = datetime.now(timezone.utc).isoformat() with open(state_path, "w") as f: json.dump(state, f, indent=2) diff --git a/openspec/changes/agent-codex-codex-task-2026-05-12-10-39/.openspec.yaml b/openspec/changes/agent-codex-codex-task-2026-05-12-10-39/.openspec.yaml new file mode 100644 index 00000000..40cc12f4 --- /dev/null +++ b/openspec/changes/agent-codex-codex-task-2026-05-12-10-39/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-05-12 diff --git a/openspec/changes/agent-codex-codex-task-2026-05-12-10-39/proposal.md b/openspec/changes/agent-codex-codex-task-2026-05-12-10-39/proposal.md new file mode 100644 index 00000000..dd9e09cb --- /dev/null +++ b/openspec/changes/agent-codex-codex-task-2026-05-12-10-39/proposal.md @@ -0,0 +1,14 @@ +## Why + +- 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. +- The hook should run cleanly on the Python versions commonly exposed as `python3` in agent shells. + +## What Changes + +- Replace `datetime.UTC` with `datetime.timezone.utc` in the Claude and Codex edit-tracker hook copies. +- Add focused regression coverage that executes the Claude `PostToolUse` edit tracker through system `python3`. + +## Impact + +- Affects only local agent hook bookkeeping for edited files. +- No runtime package behavior changes outside hook compatibility. diff --git a/openspec/changes/agent-codex-codex-task-2026-05-12-10-39/specs/codex-task/spec.md b/openspec/changes/agent-codex-codex-task-2026-05-12-10-39/specs/codex-task/spec.md new file mode 100644 index 00000000..ce1af644 --- /dev/null +++ b/openspec/changes/agent-codex-codex-task-2026-05-12-10-39/specs/codex-task/spec.md @@ -0,0 +1,9 @@ +## ADDED Requirements + +### Requirement: PostToolUse edit tracker Python compatibility +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. + +#### Scenario: Claude edit tracker hook starts cleanly +- **WHEN** Claude Code invokes `.claude/hooks/post_edit_tracker.py` with a valid `PostToolUse` payload +- **THEN** the hook exits with status `0` +- **AND** no `ImportError` traceback is emitted for `datetime.UTC`. diff --git a/openspec/changes/agent-codex-codex-task-2026-05-12-10-39/tasks.md b/openspec/changes/agent-codex-codex-task-2026-05-12-10-39/tasks.md new file mode 100644 index 00000000..851f11e9 --- /dev/null +++ b/openspec/changes/agent-codex-codex-task-2026-05-12-10-39/tasks.md @@ -0,0 +1,34 @@ +## Definition of Done + +This change is complete only when **all** of the following are true: + +- Every checkbox below is checked. +- The agent branch reaches `MERGED` state on `origin` and the PR URL + state are recorded in the completion handoff. +- 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. + +## Handoff + +- Handoff: change=`agent-codex-codex-task-2026-05-12-10-39`; branch=`agent//`; scope=`TODO`; action=`continue this sandbox or finish cleanup after a usage-limit/manual takeover`. +- Copy prompt: Continue `agent-codex-codex-task-2026-05-12-10-39` on branch `agent//`. 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// --base dev --via-pr --wait-for-merge --cleanup`. + +## 1. Specification + +- [x] 1.1 Finalize proposal scope and acceptance criteria for `agent-codex-codex-task-2026-05-12-10-39`. +- [x] 1.2 Define normative requirements in `specs/codex-task/spec.md`. + +## 2. Implementation + +- [x] 2.1 Implement scoped behavior changes. +- [x] 2.2 Add/update focused regression coverage. + +## 3. Verification + +- [x] 3.1 Run targeted project verification commands. +- [x] 3.2 Run `openspec validate agent-codex-codex-task-2026-05-12-10-39 --type change --strict`. +- [x] 3.3 Run `openspec validate --specs`. + +## 4. Cleanup (mandatory; run before claiming completion) + +- [ ] 4.1 Run the cleanup pipeline: `gx branch finish --branch agent// --base dev --via-pr --wait-for-merge --cleanup`. This handles commit -> push -> PR create -> merge wait -> worktree prune in one invocation. +- [ ] 4.2 Record the PR URL and final merge state (`MERGED`) in the completion handoff. +- [ ] 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). diff --git a/test/post-edit-tracker-hook.test.js b/test/post-edit-tracker-hook.test.js new file mode 100644 index 00000000..a928b1fe --- /dev/null +++ b/test/post-edit-tracker-hook.test.js @@ -0,0 +1,24 @@ +const test = require('node:test'); +const assert = require('node:assert/strict'); +const cp = require('node:child_process'); +const path = require('node:path'); + +const repoRoot = path.resolve(__dirname, '..'); + +test('PostToolUse edit tracker runs on Python 3.10 compatible datetime APIs', () => { + const payload = JSON.stringify({ + session_id: 'python-compat', + cwd: repoRoot, + tool_input: { + file_path: path.join(repoRoot, 'README.md'), + }, + }); + + const result = cp.spawnSync('python3', ['.claude/hooks/post_edit_tracker.py'], { + cwd: repoRoot, + input: payload, + encoding: 'utf8', + }); + + assert.equal(result.status, 0, result.stderr || result.stdout); +});