Codex vs Claude Code is less about finding one universal winner and more about choosing the working style you can trust every day. Both products can inspect a repository, edit multiple files, run commands, execute tests, and explain a patch. The meaningful differences appear in how you delegate work, how often you steer the agent, how much evidence you can inspect, and how each tool fits your existing development environment.
In our controlled four-task repository test, Codex scored 98/100 and Claude Code scored 99/100. Both completed the core work. Claude Code showed broader review and edge-case coverage, while Codex often reached the required result with a smaller scope and produced stronger run-level evidence in our setup. A one-point difference on a small Python fixture is not a reason to declare a global winner.
Developers who do not want the rest of their workflow locked to one coding agent can add GlobalGPT as a separate multi-model and multimodal layer. GlobalGPT integrates 100+ Top Models, such as GPT 5.6, Claude Opus 5 and GPT Image 2 in one dashboard. Moreover, The GlobalGPT CLI, MCP, and Skill routes can be used from Codex or Claude Code for second opinions, planning, documentation, or other supported models, while the native coding host keeps control of repository edits and tests.
The comparison combines official plan information, hands-on repository work, complete expandable outputs, and clearly labeled community experiences. The goal is to help an independent developer choose a primary coding agent without confusing subscription access, extra credits, and API billing.
Codex vs Claude Code at a glance
| Factor decisivo | Códice | Código Claude |
|---|---|---|
| Core repository work | Reads, edits, runs commands, and verifies changes across supported Codex surfaces | Conversational terminal agent for reading, editing, running commands, and verifying changes |
| Observed style in our test | More compact in parts of implementation and bug repair | More exhaustive in repository analysis, tests, and review breadth |
| Repository understanding | Functional tie on the frozen task | |
| Revisión del código | Found and fixed two valid higher-severity defects | Reported more reproducible issues and led 19/20 to 18/20 |
| Observed speed | Mixed; the environments were not equivalent enough for a general winner | |
| Consumer entry tier | $20/month through the relevant ChatGPT plan | Claude Pro: $20/month in the US |
| Higher-use tiers | $100 and $200 tiers | Max 5x at $100 and Max 20x at $200 |
The table describes a buying decision, not a model leaderboard. A different model setting, repository, permission configuration, or task specification can change the result. If you already use one product, the best comparison is a bounded task from your own codebase with the same success command and no quality rerolls.
Controlled test profile
First valid result, same frozen rubric. Bars are normalized within each task’s maximum score.
The visual shows where the one-point difference came from; it does not turn a small fixture into a universal ranking.
What Codex and Claude Code actually are
Codex and Claude Code are agent products, not simply model names. The underlying AI model for coding matters, but the surrounding harness also decides which files the agent can see, which commands it may run, how approvals work, how context is retained, and what evidence survives after the task. Comparing only model reputation ignores much of the experience a developer is purchasing.
Codex spans command-line, IDE, desktop, and cloud-oriented workflows. That range makes it suitable for both direct local collaboration and more bounded delegation. Claude Code is centered on a conversational terminal workflow with supported integrations and a substantial customization surface; this guide to using Claude for coding provides a broader introduction to that workflow. For some developers, watching an agent work in the terminal is reassuring. For others, the important artifact is the final diff, tests, and audit trail rather than a continuous conversation.
The distinction also explains why two tests using nominally strong models may behave differently. The host decides how instructions are presented, how tools are called, and when a human must approve an action. Community comparisons that ignore the harness can mistake a product-level behavior for a model-level truth.
- El modelo supplies reasoning and generation capability.
- The agent harness controls files, commands, context, approvals, and recovery.
- The user’s task contract determines scope, success checks, and when the agent should stop.

Workflow and control: delegation or continuous steering?
The most useful Codex vs Claude Code question is often not “Which writes better code?” but “How do I want to work with it?” A clear, bounded task can be delegated with an exact objective, allowed scope, and verification command. An ambiguous refactor benefits from discussion, intermediate inspection, and a chance to redirect the agent before it changes too much.
Continuous steering is valuable when requirements are evolving or architectural judgment is still being negotiated. It becomes a cost when the agent repeatedly asks for decisions that could have been resolved from repository instructions. Autonomous execution is valuable when the task contract is stable. It becomes risky when the agent makes unchecked assumptions or expands scope without a clear rollback path.
One detailed Reddit report dated April 13, 2026 described roughly 100 hours with Claude Code and 20 hours with Codex on an approximately 80,000-line Python and TypeScript project with about 2,800 tests. The author characterized Claude as faster and more interactive but needing more babysitting, and Codex as slower and more deliberate. That report is unusually useful because it includes project and experience context, but it still represents one developer’s workflow.

Our own timings did not reproduce a simple winner. Claude finished the first two tasks faster, while Codex finished the latter two faster. Claude also used a subagent fallback after its CLI authentication path was unavailable, so the execution routes were not laboratory-equivalent. The responsible conclusion is that speed depends on the task, selected model, effort setting, context, and host—not that either product is always faster.
Repository understanding and context management
Repository understanding is more than naming folders. A useful agent must trace how data moves between files, identify contracts that constrain a change, locate tests, distinguish a symptom from its likely source, and explain the risk of editing the wrong layer. An exhaustive map can reveal hidden inconsistencies; a concise map can get a developer to a safe implementation decision faster.
In our read-only T1 task, both agents earned 20/20 and grounded their answers in the repository. Claude Code was more exhaustive and surfaced additional contract inconsistencies. Codex was more compact while still identifying the relevant files, risks, and implementation plan. Neither style is automatically superior. Exhaustiveness helps during unfamiliar migrations and audits; concision helps when the developer already knows the architecture and wants a focused change plan.
T1 · Controlled repository test
Understand an unfamiliar repository before changing it
Map architecture, trace data flow, identify priority-feature risks, and make no file changes.
Architecture: model, repository, service, and an unwired time utility. Major risks: undefined priority semantics, compatibility, ordering, validation, and scope mismatch.
Reviewer take: Compact, file-grounded, and sufficient for the frozen decision.
Three layers plus one orphan module. No cycles; minimal dependency injection. It also surfaced identifier allocation, asymmetric error contracts, and thin test coverage.
Reviewer take: More exhaustive while still satisfying the same read-only contract.
View full Codex output
Complete sanitized first-valid output
## Architecture
This is a small, standard-library-only Python package with four functional layers:
- **Data model:** [`Job`](<./scheduler/models.py:4>) is an immutable dataclass containing `identifier`, `name`, and `owner`. `Job.to_dict()` serializes it through `dataclasses.asdict()`.
- **Repository:** [`JobRepository`](<./scheduler/repository.py:4>) stores jobs in an in-memory dictionary keyed by identifier. There is no database or durable persistence.
- **Service:** [`SchedulerService`](<./scheduler/service.py:5>) owns identifier allocation, input normalization, creation, listing, and deletion.
- **Time utility:** [`next_daily_run()`](<./scheduler/time_rules.py:4>) calculates a subsequent run time, but nothing imports or connects it to `Job`, `SchedulerService`, or `JobRepository`.
- **Public API:** [`scheduler.__init__`](<./scheduler/__init__.py:3>) exports only `SchedulerService`.
Despite the repository name, jobs do not currently contain scheduling information, and there is no execution queue. Priority can therefore affect stored metadata and listing order, but it cannot affect actual job execution without broadening the system considerably.
## Creation and listing data flow
Creation through `SchedulerService.create_job(name, owner)`:
1. [`create_job()`](<./scheduler/service.py:10>) rejects a whitespace-only name.
2. It trims `name` and `owner`, then constructs an immutable `Job` using the service’s `_next_identifier`.
3. [`JobRepository.save()`](<./scheduler/repository.py:8>) writes the object to `_jobs[job.identifier]`.
4. The service increments `_next_identifier` and returns the same `Job`.
Listing through `SchedulerService.list_jobs()`:
1. [`list_jobs()`](<./scheduler/service.py:18>) delegates directly to `repository.all()`.
2. [`JobRepository.all()`](<./scheduler/repository.py:15>) sorts dictionary keys numerically.
3. Jobs are therefore returned in ascending identifier/creation order, not schedule or priority order.
## Likely files affected by job priorities
Required:
- [`scheduler/models.py`](<./scheduler/models.py:4>): add the priority field and any default. `to_dict()` will automatically include it because it uses `asdict()`.
- [`scheduler/service.py`](<./scheduler/service.py:10>): accept, validate, and pass priority into `Job`.
- [`tests/test_scheduler.py`](<./tests/test_scheduler.py:6>): cover default, explicit, invalid, serialized, and ordering behavior.
Conditional:
- [`scheduler/repository.py`](<./scheduler/repository.py:15>): change `all()` only if priorities are intended to control listing order. A deterministic secondary key such as `identifier` should handle ties.
- [`scheduler/__init__.py`](<./scheduler/__init__.py:3>): update only if priority becomes a public enum or value object.
- [`README.md`](<./README.md:1>): document the accepted values, default, and ordering semantics.
Probably unaffected:
- `scheduler/time_rules.py`, because its only symbol operates on a supplied datetime and has no relationship to jobs or listing.
## Major risks
- **Undefined semantics:** The code provides no precedent for whether larger or smaller values mean higher priority, which values are valid, or whether priority affects listing at all.
- **Compatibility:** Adding a required argument to `Job` or `create_job()` would break existing callers such as `create_job("Daily report", "Alex")` in the current test. A default preserves that call.
- **Positional-constructor breakage:** `Job` is currently constructed positionally in `SchedulerService.create_job()`. Field placement and future positional calls could silently assign the wrong values; keyword construction would be safer.
- **Changed ordering:** Replacing identifier ordering in `JobRepository.all()` changes established behavior. Equal priorities need a stable tie-breaker.
- **Validation ambiguity:** An unrestricted Python value could allow strings, booleans, or arbitrary integers that are difficult to compare consistently.
- **Execution-scope mismatch:** There is no dispatcher or run queue. Claiming that priority changes which job executes first would not be supported by any current symbol.
- **In-memory limitations:** Priority, like every other job field, disappears when the process ends because `_jobs` is only an instance dictionary.
## Implementation and validation plan
1. Define the contract first: representation, allowed values, default, priority direction, tie-breaking, and whether it changes listing order.
2. Add `priority` to `Job`, preferably with a backward-compatible default.
3. Extend `SchedulerService.create_job()` with a defaulted priority parameter, validate it, and construct `Job` with keyword arguments.
4. If listing is priority-aware, change `JobRepository.all()` to sort by priority first and identifier second. Otherwise leave repository ordering unchanged.
5. Add tests for:
- Existing two-argument creation.
- Explicit priority preservation.
- Default priority.
- Invalid type and out-of-range values.
- `Job.to_dict()` output.
- Multiple-priority listing order.
- Stable ordering for equal priorities.
- Existing blank-name behavior.
6. Update the README with the exact public contract.
7. Run the complete offline suite with `python3 -m unittest discover -s tests -v`.
## Verification performed
- Baseline test suite: **2 tests passed**.
- Repository-wide symbol search confirmed `next_daily_run()` is disconnected and there are no existing priority symbols.
- Files changed: **none**.
- Feature implementation: **not performed**, as required.
> **Conclusion + decision**
>
> The change is small if priority is only metadata or listing order: primarily `models.py`, `service.py`, `repository.py`, and tests. Before implementation, the required decision is whether priority merely describes a job or changes `list_jobs()` ordering, plus the value scale, default, direction, and tie-break rule.View full Claude Code output
Complete handoff record including the verbatim first-valid output
# T1 Run Record — Unfamiliar Repository Understanding
## Execution parameters
| Field | Value |
| --- | --- |
| Task | `T1` |
| Frozen prompt | `01-prompts/T1.md` |
| Frozen prompt SHA-256 | `a571ff15c2502ec29b172ae2e77dd9ab0caca2869b91f9468d47571434d16c17` |
| Execution unit | isolated Claude subagent, `general-purpose`, fresh context |
| Isolation method | **subagent fallback** (documented downgrade — see `results/isolation-decision.md`) |
| Assigned clean copy | `runtime/workspaces/T1` |
| Claude Code version | `2.1.220` |
| Model setting | `claude-opus-5` |
| start_utc | `2026-07-28T10:27:31Z` |
| end_utc | `2026-07-28T10:28:32Z` |
| Duration | 61 s (concurrent with T2–T4; not a clean latency measurement) |
| Attempt number | 1 |
| Status | `valid` |
| Human intervention | none |
## Safe tool summary
`date`, `find`, `ls`, `wc`, `stat`, `grep`, `Read` on 7 workspace files, `python3 -V`,
`python3 -m unittest discover -s tests -v`. All reads confined to the assigned workspace.
No network, no installs, no writes.
## Offline check result
`python3 -m unittest discover -s tests -v` → 2 tests, OK (exit 0). Re-verified by the
coordinator from outside the workspace: see `results/checks/T1-public.txt`.
## Patch
None. `results/T1/patch.diff` is empty, and `diff -ru 02-fixture runtime/workspaces/T1`
returns no differences. The task required inspection without changing files; this is
confirmed rather than asserted.
## Sanitized errors
One recoverable shell error: an initial `grep` used unquoted `--include=*.py` patterns that
the shell attempted to glob; re-run with quoted patterns and succeeded. No effect on findings.
## Visible output (verbatim)
### 1. What this repository is
A self-contained Python standard-library fixture named `local-scheduler-1.0.0` (`fixture-manifest.json`), MIT-licensed, with no network or production dependency (`README.md`, `"network_required": false`). It is very small: 8 source/doc files plus the manifest, 103 total lines. Interpreter present is **Python 3.9.6**. There is no `pyproject.toml`, `setup.py`, CI config, linter, or type-checker config — the only declared workflow is `python3 -m unittest discover -s tests -v` (`README.md`).
### 2. Architecture
Three layers plus one orphan module, all under `scheduler/`:
| Layer | File | Symbol | Responsibility |
|---|---|---|---|
| Package façade | `scheduler/__init__.py` | `__all__ = ["SchedulerService"]` | Re-exports only `SchedulerService`; `Job`, `JobRepository`, and `time_rules` are not part of the public surface |
| Domain model | `scheduler/models.py` | `Job` (`@dataclass(frozen=True)`) | Immutable value object with fields `identifier: int`, `name: str`, `owner: str`, plus `to_dict()` implemented as `asdict(self)` |
| Persistence | `scheduler/repository.py` | `JobRepository` | In-memory `dict` keyed by identifier; `save`, `get`, `all`, `delete` |
| Application service | `scheduler/service.py` | `SchedulerService` | Validation, identifier allocation, orchestration: `create_job`, `list_jobs`, `delete_job` |
| Orphan | `scheduler/time_rules.py` | `next_daily_run` | Recurrence arithmetic — **imported by nothing** |
The dependency direction is clean and one-way: `service.py` imports from `.models` and `.repository`; `repository.py` imports from `.models`; `models.py` imports only `dataclasses`. No cycles. Dependency injection is present but minimal — `SchedulerService.__init__(self, repository=None)` does `self.repository = repository or JobRepository()`, so a fake repository can be substituted in tests.
Note that `scheduler/time_rules.py` is dead code today. I grepped the whole tree for `time_rules` and `next_daily_run`; the only hits are its own definition and its listing in `fixture-manifest.json`'s `expected_files`. It is shipped but unwired.
### 3. Data flow
**Creating a job** — `SchedulerService.create_job(name, owner)`:
1. Guard: `if not name.strip(): raise ValueError("name is required")`. This is the *only* validation in the system — `owner` is never checked, so a blank owner is accepted.
2. Construct: `Job(self._next_identifier, name.strip(), owner.strip())` — positional construction, and both strings are normalized by stripping.
3. Persist: `self.repository.save(job)`, which does `self._jobs[job.identifier] = job` and returns the job.
4. Advance the counter: `self._next_identifier += 1` — *after* the save, so a raised exception leaves the counter unconsumed.
5. Return the `Job` instance (the service returns its own local `job`, not the repository's return value).
Identifier allocation lives in the **service**, not the repository (`self._next_identifier = 1` in `SchedulerService.__init__`). This is an important seam: injecting a repository that already contains jobs will silently overwrite them, because the fresh service restarts numbering at 1.
**Listing jobs** — `SchedulerService.list_jobs()` is a straight pass-through to `JobRepository.all()`, which returns `[self._jobs[key] for key in sorted(self._jobs)]`. Ordering is therefore **ascending by integer identifier**, which today coincides with insertion order. A new list is built each call, but the `Job` objects are shared references — safe only because `Job` is frozen.
**Deleting** — `SchedulerService.delete_job(identifier)` delegates to `JobRepository.delete`, which is `self._jobs.pop(identifier)` with no default, so it raises `KeyError` on a missing id. This is inconsistent with `JobRepository.get`, which returns `None` for the same condition.
**Test coverage of these paths** — `tests/test_scheduler.py` has exactly two cases: `test_create_and_list_jobs` (asserts `job.identifier == 1` and `[job] == service.list_jobs()`) and `test_blank_name_is_rejected`. Both pass on the current tree.
### 4. Files likely affected by adding job priorities
In descending order of certainty:
1. **`scheduler/models.py`** — certain. `Job` needs a `priority` field. Because the dataclass is `frozen=True` and fields are positional, a new field must carry a default (e.g. `priority: int = 0`) or it must be placed after the existing three, otherwise every existing positional construction breaks. `to_dict()` needs no edit — `asdict(self)` picks the field up automatically, which is a silent serialization-contract change rather than a code change.
2. **`scheduler/service.py`** — certain. `create_job` needs a `priority` parameter and must pass it into the `Job(...)` positional constructor at line 13. If priority is validated (range, enum membership), the guard belongs next to the existing `name.strip()` check. If listing must be priority-ordered, `list_jobs` is where an ordering policy would sit if you want the repository to stay a dumb store.
3. **`scheduler/repository.py`** — likely. `JobRepository.all()`'s `sorted(self._jobs)` sorts dict *keys*, i.e. identifiers. Priority-ordered listing requires changing this to sort the values by a key such as `(-job.priority, job.identifier)`, or adding a separate ordered accessor. Do not leave the tie-break implicit: dict insertion order is not a stable contract to depend on.
4. **`tests/test_scheduler.py`** — certain. New cases for default priority, explicit priority, ordering, and invalid-priority rejection. Note that the existing `assertEqual([job], service.list_jobs())` compares the *same* object, so it survives a field addition; it would not survive if it compared a freshly constructed `Job` literal.
5. **`README.md`** — likely, if the priority semantics (which end of the range wins, default value) need to be stated anywhere.
6. **`scheduler/__init__.py`** — only if a priority enum or constants need exporting alongside `SchedulerService`.
7. **`fixture-manifest.json`** — only if you add a new module file; its `expected_files` array is an explicit inventory and would drift.
8. **`scheduler/time_rules.py`** — should *not* be touched. Priority is orthogonal to recurrence, and this module is unwired.
### 5. Major risks
- **Ordering semantics are a silent behavior change.** `JobRepository.all()` currently guarantees ascending-identifier order and `list_jobs` inherits it. Any consumer relying on that guarantee is broken by re-sorting. Decide explicitly: does `list_jobs()` change meaning, or does a new `list_jobs_by_priority()` appear alongside it? The former is the higher-risk option and the existing test `test_create_and_list_jobs` is too weak to catch a regression (single-element list).
- **Sort direction is genuinely ambiguous.** "Priority 1" conventionally means *highest* in some systems and *lowest* in others. There is nothing in this repo — no comment, no docstring, no README line — that settles it. Pick a convention, document it in `models.py`, and encode it in a test with at least three distinct priorities so the direction is pinned.
- **Unstable sort ties.** If two jobs share a priority, the result must still be deterministic. Python's `sorted` is stable, but stability is only useful if the input order is itself defined. Always include `identifier` as an explicit secondary key.
- **Frozen-dataclass field ordering.** `Job` is `@dataclass(frozen=True)` with three positional fields, and `service.py:13` constructs it positionally. Inserting `priority` anywhere other than last, or without a default, is a breaking change that the two existing tests may not surface loudly.
- **`to_dict()` contract widening.** `asdict(self)` reflects all fields. Adding `priority` changes every serialized payload with no visible edit to `models.py`'s method body. Anything asserting on exact dict shape breaks.
- **Python 3.9.6 constraints.** No `@dataclass(slots=True)` (3.10+), no `kw_only=True` (3.10+), and PEP 604 `int | None` annotations fail at runtime without `from __future__ import annotations`. If priority is modeled as an enum, `enum.StrEnum` is 3.11+ and unavailable. Any of these would break the fixture on this interpreter.
- **Identifier allocation lives in the wrong layer.** `SchedulerService._next_identifier` means the service owns id generation while the repository owns storage. If priority work motivates a second repository implementation or preloaded fixtures, this split will produce id collisions and overwritten jobs.
- **Asymmetric error contracts.** `JobRepository.get` returns `None` on miss while `JobRepository.delete` raises `KeyError`. Any new priority-related lookup should pick one convention deliberately rather than inherit the inconsistency by accident.
- **Validation gap precedent.** `create_job` validates `name` but not `owner`. Do not copy that laxity for `priority` — an unvalidated priority propagates into the sort key and can raise `TypeError` deep inside `sorted` (e.g. comparing `int` to `None`) rather than at the call site.
- **Dead-code trap.** `scheduler/time_rules.py` looks like scheduling logic and will attract edits. It is imported by nothing, and its own docstring admits the bug: it adds 24 elapsed hours in UTC and "shifts local wall-clock time across daylight-saving transitions." Changing it produces zero observable effect on the tested system, so effort spent there is wasted and any test written against it validates nothing that ships.
- **Thin safety net.** Two tests, no CI, no linter, no type checking. There is nothing to catch a regression except what you write yourself. Treat the existing suite as insufficient baseline coverage, not as a gate.
### 6. Implementation plan (not executed)
1. **Settle semantics first, in writing.** Priority type (`int` vs enum), valid range, default for existing/unspecified jobs, and sort direction. Record it as a docstring on `Job` in `scheduler/models.py` — that is the only place all layers already depend on.
2. **Extend the model.** Add `priority: int = 0` as the *last* field of `Job` in `scheduler/models.py`, preserving positional compatibility with `service.py:13` and leaving `to_dict()` untouched.
3. **Thread it through the service.** Add a defaulted `priority` parameter to `SchedulerService.create_job`, validate it beside the existing `name.strip()` guard (raise `ValueError`, matching the established error type), and pass it to the `Job` constructor.
4. **Decide the listing contract explicitly.** Preferred low-risk option: leave `JobRepository.all()` alone as the identifier-ordered primitive, and add a separate priority-ordered accessor. If instead `list_jobs()` must change meaning, change `JobRepository.all()`'s comprehension to sort values by an explicit compound key including `identifier` as tie-break, and note the change in `README.md`.
5. **Keep `scheduler/__init__.py` in sync** only if a public enum or constant was introduced.
6. **Do not touch `scheduler/time_rules.py`**, and do not add files unless `fixture-manifest.json`'s `expected_files` is updated in the same change.
### 7. Validation plan
- **Baseline first.** Run `python3 -m unittest discover -s tests -v` before any edit and confirm 2 passing. I already did this — it passes clean.
- **Regression gate.** Both existing tests must still pass unmodified. If `test_create_and_list_jobs` needs editing, that is a signal the change is breaking, not a reason to edit the test.
- **New cases in `tests/test_scheduler.py`:**
- Default priority when `create_job` is called with the old two-argument signature (proves backward compatibility).
- Explicit priority round-trips through `create_job` → `list_jobs`.
- Ordering with **three or more** distinct priorities, asserting the full list — this is what pins sort direction.
- Tie-break: two jobs at equal priority come back in ascending-identifier order.
- Invalid priority raises `ValueError`, mirroring the blank-name case.
- `to_dict()` includes `priority` with the expected value.
- Injected-repository path: `SchedulerService(repository=fake)` still assigns priority correctly, exercising the `repository or JobRepository()` seam.
- **Compatibility check.** Confirm everything runs on Python 3.9.6 specifically — no `slots=`, no `kw_only=`, no bare PEP 604 unions, no `StrEnum`.
- **Inventory check.** Re-read `fixture-manifest.json` and confirm the on-disk file set still matches `expected_files`.
- **Determinism.** Run the suite two or three times; sorting bugs involving dict iteration order can present intermittently.
- **Cleanliness.** Run with `PYTHONDONTWRITEBYTECODE=1` so `__pycache__` directories do not appear as untracked drift against the manifest.
Fixture limitation: this was a small offline Python repository. Claude used a subagent fallback after CLI OAuth expired, so isolation and token visibility were not identical.
This result does not establish performance on a large monorepo or a week-long autonomous session. The fixture was deliberately small and controlled. For a real evaluation, start with a read-only prompt: ask each tool to map the call path, name the files it would change, identify tests, and list unresolved assumptions before allowing edits.
Implementation style, patch scope, and bug fixing
Multi-file feature work
The same passing behavior can come from different engineering choices. In T2, both products earned 30/30 and passed the frozen hidden validator. Claude Code placed validation at more than one boundary and added broader direct-model and threshold tests. Codex used a smaller service-centered implementation that also met the contract.
For an independent developer, this is a genuine trade-off. Broader validation can protect invariants when objects are created through multiple paths. A smaller patch can reduce regression surface and review time when one service is the intended entry point. The right answer depends on the repository’s ownership rules, not on counting changed lines or tests in isolation.
T2 · Controlled repository test
Implement a priority feature across multiple files
Add the frozen behavior, preserve compatibility, and pass public plus hidden checks.
Added priority to the immutable model, validated it at the service boundary, and kept ordering in the repository. All seven public tests and the frozen hidden validator passed.
Reviewer take: A smaller service-centered implementation that met every frozen behavior.
Validated priority at both the service boundary and immutable model, preserved repository filtering, and added threshold coverage. Fourteen public tests and the hidden validator passed.
Reviewer take: Broader direct-model and threshold coverage without a functional score advantage.
View full Codex output
Complete sanitized first-valid output
Implemented the scheduled-job priority feature. Files changed: - [scheduler/models.py](./scheduler/models.py:5) — added serialized `priority`, defaulting to `0`. - [scheduler/service.py](./scheduler/service.py:11) — added validation, storage, and `list_jobs(min_priority=0)` filtering while preserving identifier order. - [tests/test_scheduler.py](./tests/test_scheduler.py:16) — added coverage for defaults, serialization, storage, boundaries, invalid values, and filtering. Tests run: ```text python3 -m unittest discover -s tests -v Ran 7 tests — OK ``` **Conclusion:** The frozen task is complete and verified. **Decision required:** None.
View full Claude Code output
Complete handoff record including the verbatim first-valid output
# T2 Run Record — Multi-File Priority Feature ## Execution parameters | Field | Value | | --- | --- | | Task | `T2` | | Frozen prompt | `01-prompts/T2.md` | | Frozen prompt SHA-256 | `8476a56034c4e719b97ee4081b32b4c73efd45feafe1bda9d477c83cb8fdb698` | | Execution unit | isolated Claude subagent, `general-purpose`, fresh context | | Isolation method | **subagent fallback** (documented downgrade — see `results/isolation-decision.md`) | | Assigned clean copy | `runtime/workspaces/T2` | | Claude Code version | `2.1.220` | | Model setting | `claude-opus-5` | | start_utc | `2026-07-28T10:27:52Z` | | end_utc | `2026-07-28T10:29:33Z` | | Duration | 101 s (concurrent with T1, T3, T4; not a clean latency measurement) | | Attempt number | 1 | | Status | `valid` | | Human intervention | none | ## Safe tool summary `date`, `Read`/`Edit`/`Write` inside the assigned workspace only, `grep`, `python3 -m unittest discover`, `python3 -m compileall`, inline `python3 -c` smoke checks. No network, no installs, standard library only. ## Files changed `scheduler/models.py`, `scheduler/repository.py`, `scheduler/service.py`, `tests/test_scheduler.py`. No files added or removed, so `fixture-manifest.json` remains accurate. Full patch: `results/T2/patch.diff`. ## Offline check results Reported by the task runner: | Check | Result | | --- | --- | | `python3 -m unittest discover -s tests -v` (baseline, pre-change) | 2 passed, 0 failed | | `python3 -m compileall -q scheduler tests` | clean | | inline smoke checks (legacy 3-arg construction, keyword/positional threshold, `to_dict`) | passed | | `python3 -m unittest discover -s tests -v` (final) | **14 passed, 0 failed** | Independently re-verified by the coordinator from outside the workspace: | Check | Exit | Result | | --- | --- | --- | | public suite (`results/checks/T2-public.txt`) | 0 | 14 tests, OK | | hidden validator `03-checks/test_t2_hidden.py` (`results/checks/T2-hidden.txt`) | 0 | 2 tests, OK | The hidden validator fails (exit 1) against a pristine fixture copy, confirming the pass is meaningful. See `results/checks/validator-controls.md`. ## Gold-map behavior coverage All five required behaviors in `03-checks/gold-map.json` are satisfied: | Required behavior | Evidence | | --- | --- | | priority default 0 | `Job.priority: int = MIN_PRIORITY`; `create_job(..., priority=MIN_PRIORITY)`; `test_priority_defaults_to_zero` | | integer range 0 through 9 | `validate_priority` rejects non-int and out-of-range; `test_out_of_range_priority_is_rejected`, `test_non_integer_priority_is_rejected` | | serialized priority | `to_dict()` via `asdict`; `test_priority_appears_in_serialized_output` asserts the exact dict | | minimum priority filtering | `list_jobs(min_priority=0)` → `JobRepository.all(min_priority)`; `test_filter_is_inclusive_of_the_threshold` | | identifier ordering | filter applied inside the existing `sorted(self._jobs)` comprehension; `test_filter_preserves_ascending_identifier_order` | ## Sanitized errors None. `__pycache__` artifacts from test runs were removed by the task runner; a coordinator sweep confirmed the workspace is clean. ## Visible output (verbatim) Added an integer `priority` field to scheduled jobs across the model, repository, and service layers of the local scheduler fixture, plus filtered listing. **`scheduler/models.py`** — Added module constants `MIN_PRIORITY = 0` / `MAX_PRIORITY = 9` and a reusable `validate_priority(priority)` function that raises `ValueError` for non-integers and for values outside 0–9 inclusive. `bool` is explicitly rejected despite being an `int` subclass, so `True` cannot slip through as priority 1. The frozen `Job` dataclass gains `priority: int = MIN_PRIORITY` as a trailing field with a default, which keeps existing 3-argument construction working, and a `__post_init__` that calls the validator so invalid priorities cannot be constructed by any path. `to_dict()` was left as `asdict(self)`, so priority appears in serialized output automatically and in field order. **`scheduler/repository.py`** — `JobRepository.all()` takes an optional `min_priority` and filters within the existing `sorted(self._jobs)` comprehension, so ascending identifier order is guaranteed by the same single code path that already provided it rather than being re-established after filtering. The default argument preserves the previous no-arg behavior for existing callers. Storage itself needed no change: the repository stores whole `Job` instances, so priority persists and round-trips through `save`/`get`/`all`. **`scheduler/service.py`** — `create_job` accepts an optional trailing `priority` argument (default 0) and validates it before constructing the `Job`, so the failure is raised at the service boundary with the same `ValueError` contract as the existing blank-name check. `list_jobs(min_priority=0)` delegates to the repository. Ordering and filtering live in one place rather than being duplicated. **Design note on `min_priority` validation:** it is type-checked as an integer but deliberately *not* range-restricted to 0–9. The 0–9 rule constrains what a job may store; a threshold is a query parameter, and rejecting e.g. `min_priority=10` would turn a legitimate "match nothing" query into an error. `list_jobs(min_priority=9)` returning `[]` when no job has priority 9 is covered by a test. `scheduler/time_rules.py` was read but left untouched — its documented daylight-saving shift is pre-existing seeded behavior, out of scope for this task, and changing it would have been an unrelated behavior change. No files were added or removed, so `fixture-manifest.json` remains accurate (verified programmatically). ### Added test coverage Default of 0; storage round-trip through `JobRepository`; priority in `to_dict()` output; boundary values 0 and 9 accepted; out-of-range −1/10/100 rejected; non-integer `"3"`/`3.5`/`None`/`True` rejected; direct model-level validation; unfiltered listing order; filtered listing preserving ascending identifier order; inclusive threshold; empty result; non-integer `min_priority` rejected.
Fixture limitation: this was a small offline Python repository. Claude used a subagent fallback after CLI OAuth expired, so isolation and token visibility were not identical.
Bug fixing under edge cases
T3 tested a daylight-saving-time scheduling defect. Both agents earned 30/30 and passed the same spring and autumn hidden checks. Claude Code added explicit coverage for ambiguous and skipped local times, naive inputs, and multi-day rolls. Codex made a narrower calendar-day change and passed the frozen contract.
Broader edge-case tests create confidence when the library is widely reused or the cost of a scheduling error is high. A narrow change is attractive when the defect is well isolated and the team wants to minimize behavior changes. The practical review question is not “Which wrote more?” It is whether the patch fixes the stated root cause, preserves surrounding contracts, and proves the risky boundaries.
T3 · Controlled repository test
Repair daily scheduling across DST transitions
Preserve the requested New York wall-clock hour through spring and fall transitions.
Root cause: the implementation added 24 elapsed UTC hours. Fix: advance by one local calendar day. Both frozen spring and fall checks passed.
Reviewer take: The narrower patch passed the complete frozen contract.
Covered spring, fall, skipped and ambiguous local times, naive input, and multi-day rolls. Both frozen hidden checks passed after broader regression coverage.
Reviewer take: Broader edge-case coverage; the same functional score under the frozen rubric.
View full Codex output
Complete sanitized first-valid output
Implemented the DST fix. Files changed: - [scheduler/time_rules.py](./scheduler/time_rules.py) - [tests/test_time_rules.py](./tests/test_time_rules.py) Changes: - Advances one local calendar day instead of 24 UTC hours. - Added New York spring-forward and fall-back regression tests. - Verified elapsed UTC intervals are 23 and 25 hours respectively. Checks: - Unit tests: 4 passed. - In-memory syntax check: 7 Python files passed. - Bytecode compilation was blocked because Python redirects its cache outside the assigned workspace; no external files were written. **Conclusion:** `next_daily_run` now preserves the requested wall-clock hour across DST transitions. No decision is required.
View full Claude Code output
Complete handoff record including the verbatim first-valid output
# T3 Run Record — DST Bug Fix
## Execution parameters
| Field | Value |
| --- | --- |
| Task | `T3` |
| Frozen prompt | `01-prompts/T3.md` |
| Frozen prompt SHA-256 | `3fee50d0230f27f8d6dd7f12e050fa3f0b7f5fb77d9559361fee96e5f1a47307` |
| Execution unit | isolated Claude subagent, `general-purpose`, fresh context |
| Isolation method | **subagent fallback** (documented downgrade — see `results/isolation-decision.md`) |
| Assigned clean copy | `runtime/workspaces/T3` |
| Claude Code version | `2.1.220` |
| Model setting | `claude-opus-5` |
| start_utc | `2026-07-28T10:28:05Z` |
| end_utc | `2026-07-28T10:30:03Z` |
| Duration | 118 s (concurrent with T1, T2, T4; not a clean latency measurement) |
| Attempt number | 1 |
| Status | `valid` |
| Human intervention | none |
## Safe tool summary
`date`, `Read`/`Edit` inside the assigned workspace only, `python3 -m unittest discover`,
`python3 -m compileall`, an in-memory negative-control run, a manifest integrity check.
Standard library only (`datetime`, `zoneinfo`, `unittest`). No network, no installs.
## Files changed
`scheduler/time_rules.py` (the fix — only the body and docstring of `next_daily_run`),
`tests/test_scheduler.py` (added `NextDailyRunTests`, 8 tests, plus an `_elapsed` helper;
existing tests unmodified). No other module touched, no files added. Full patch:
`results/T3/patch.diff`.
## Offline check results
Reported by the task runner:
| Check | Result |
| --- | --- |
| `python3 -m unittest discover -s tests -v` | **10 passed, 0 failed** (2 pre-existing + 8 new) |
| `python3 -m compileall -q scheduler tests` | clean |
| manifest integrity vs `fixture-manifest.json` | 0 missing, 0 unexpected |
| negative control: seeded implementation patched in-memory, `NextDailyRunTests` only | 8 ran, **5 failed as intended**, including both DST tests |
Independently re-verified by the coordinator from outside the workspace:
| Check | Exit | Result |
| --- | --- | --- |
| public suite (`results/checks/T3-public.txt`) | 0 | 10 tests, OK |
| hidden validator `03-checks/test_t3_hidden.py` (`results/checks/T3-hidden.txt`) | 0 | 2 tests, OK |
The hidden validator fails (exit 1) against a pristine fixture copy. See
`results/checks/validator-controls.md`.
## Gold-map behavior coverage
All four required behaviors in `03-checks/gold-map.json` are satisfied:
| Required behavior | Evidence |
| --- | --- |
| wall-clock hour preserved | increment applied to the naive local wall clock, zone re-attached; asserted in every DST test |
| spring transition covered | `test_spring_forward_keeps_requested_hour` — hour 09:30 held, offset `-04:00`, elapsed 23 h |
| fall transition covered | `test_fall_back_keeps_requested_hour` — hour 09:30 held, offset `-05:00`, elapsed 25 h |
| IANA timezone used | `ZoneInfo("America/New_York")` from the standard library |
Beyond the required set, the runner also covered the two PEP 495 edge cases — a wall time
skipped by spring-forward and an ambiguous wall time during fall-back — plus seven-day rolls
straddling each transition and naive-input behavior.
## Sanitized errors
Two self-inflicted test-assertion failures: `following - previous` on two datetimes sharing a
`tzinfo` subtracts naive wall clocks, so the elapsed-time assertions read 1 day and failed.
The implementation was correct and the assertions were wrong; corrected with an `_elapsed`
helper that converts both operands to UTC first. No tooling, import, or environment errors.
`__pycache__` artifacts were removed after the run.
## Visible output (verbatim)
**Reproduction**
`scheduler/time_rules.py::next_daily_run` was called with an `America/New_York` datetime on each side of a 2024 DST transition:
```
2024-03-09T09:30:00-05:00 -> 2024-03-10T10:30:00-04:00 # hour drifted +1
2024-11-02T09:30:00-04:00 -> 2024-11-03T08:30:00-05:00 # hour drifted -1
```
A job requested for 09:30 local fired at 10:30 after spring-forward and 08:30 after fall-back.
**Root cause**
The seeded implementation treated "daily" as a fixed absolute duration:
```python
elapsed = previous_run.astimezone(timezone.utc) + timedelta(hours=24)
return elapsed.astimezone(previous_run.tzinfo)
```
Converting to UTC first makes `timedelta(hours=24)` exactly 24 elapsed hours. But a local calendar day in an IANA zone is 23 h on the spring-forward day and 25 h on the fall-back day, so a constant 24 h offset necessarily lands one hour away from the requested wall-clock time.
**The fix** (`scheduler/time_rules.py`)
Daily recurrence is a calendar rule, so the increment is now applied to the naive local wall clock, with the zone re-attached afterwards:
```python
tzinfo = previous_run.tzinfo
naive_next = previous_run.replace(tzinfo=None) + timedelta(days=1)
if tzinfo is None:
return naive_next
candidate = naive_next.replace(tzinfo=tzinfo, fold=0)
return candidate.astimezone(timezone.utc).astimezone(tzinfo)
```
Why it is correct:
- Wall-clock arithmetic on the naive value is offset-independent, so the requested hour:minute is preserved by construction; `zoneinfo` then supplies whichever UTC offset is in force on the new date (verified: `-04:00` after spring-forward, `-05:00` after fall-back, elapsed real time 23 h and 25 h respectively).
- `fold=0` plus the UTC round-trip handles the two edge cases that pure wall-clock arithmetic leaves ill-defined. Under PEP 495, a *skipped* wall time (02:30 on 2024-03-10) has the pre-transition offset at `fold=0`, so the round-trip normalizes it to the real instant 03:30 EDT rather than returning a datetime that names no instant. An *ambiguous* wall time (01:30 on 2024-11-03) resolves deterministically to its first occurrence (EDT) instead of being arbitrary.
- Naive input keeps its previous meaning (plain +1 calendar day) — no new `tzinfo` requirement is imposed on callers.
Scope was kept narrow: only the body/docstring of `next_daily_run` changed. `service.py`, `models.py`, `repository.py`, and `__init__.py` are untouched, no new files were added, and the fixture manifest still matches exactly (no missing or unexpected files). Standard library only (`datetime`, `zoneinfo`, `unittest`).
**Regression tests** (`tests/test_scheduler.py`, new `NextDailyRunTests`, 8 tests) cover: ordinary-day stability, spring-forward hour + offset + 23 h elapsed, fall-back hour + offset + 25 h elapsed, seven-day rolls straddling each transition, skipped wall time, ambiguous wall time, and naive input.
Two notes worth recording. First, my initial elapsed-time assertions used `following - previous`; Python ignores a shared `tzinfo` and subtracts naive wall clocks, so both returned 1 day and failed. The implementation was right and the assertions wrong — fixed with an `_elapsed` helper that converts both operands to UTC. Second, I verified the tests actually catch the original defect by re-running `NextDailyRunTests` against an in-memory copy of the seeded implementation: 5 of 8 failed, including both DST tests. The three that pass under the buggy version (ordinary day, skipped, ambiguous) are behavior-documenting rather than regression-catching — the skipped/ambiguous cases coincidentally agree because a +24 h absolute shift happens to land on the same normalized instant.
Fixture limitation: this was a small offline Python repository. Claude used a subagent fallback after CLI OAuth expired, so isolation and token visibility were not identical.
Code review quality and human verification
Code-review tasks expose a different skill from feature implementation. A useful reviewer must find reproducible issues, rank their impact, cite the exact code path, and distinguish a real defect from a speculative warning. Fixing an issue adds another test: the patch must not introduce a new regression.
Claude Code had the clearest advantage in T4. It reported five independently reproducible findings, including both seeded review targets, and earned 19/20. Codex reported and fixed two valid higher-severity defects but did not mention the seeded pair, earning 18/20. Codex’s findings were not false positives; the observed difference was breadth rather than basic correctness.
T4 · Controlled repository test
Find reproducible defects and repair the highest-confidence issues
Review the fixture, reproduce real defects, apply permitted fixes, and prove regression tests bind the behavior.
1. High — Existing jobs could be silently overwritten. 2. Medium — Daily runs drifted across daylight-saving transitions. Verification: four tests passed, zero failures.
Reviewer take: Useful higher-severity findings and focused fixes, but it missed the two seeded review targets.
D1 DST drift · High D2 Naive timezone acquisition · Medium D3 Identifier collision · High D4 Missing delete raises KeyError · Medium D5 Owner unvalidated · Low
Reviewer take: Broader review coverage with zero reproduced false positives; two contract-dependent findings were not fixed.
View full Codex output
Complete sanitized first-valid output
## Actionable findings 1. **High — Existing jobs could be silently overwritten** - Evidence: identifier initialization in [service.py](./scheduler/service.py:8), combined with dictionary replacement in [repository.py](./scheduler/repository.py:9). - Impact: creating a service with an existing repository could reuse identifier `1`, replacing the stored job and causing data loss. - Minimal reproduction: save `Job(1, ...)`, construct `SchedulerService(repository)`, then create another job. Before the fix, only the new job remained. - Fix: initialize the next identifier from the repository’s highest existing identifier. - Regression test: [test_scheduler.py](./tests/test_scheduler.py:22). 2. **Medium — Daily runs drifted across daylight-saving transitions** - Evidence: corrected calculation in [time_rules.py](./scheduler/time_rules.py:4). - Impact: a daily job scheduled for 09:00 ran at 10:00 after the spring DST transition. - Minimal reproduction: calculate the next run after `2026-03-07 09:00 America/New_York`. Before the fix, the result was `2026-03-08 10:00`. - Fix: add one local calendar day instead of 24 elapsed UTC hours. - Regression test: [test_scheduler.py](./tests/test_scheduler.py:33). ## Files changed - [scheduler/service.py](./scheduler/service.py) - [scheduler/time_rules.py](./scheduler/time_rules.py) - [tests/test_scheduler.py](./tests/test_scheduler.py) ## Verification - Both regression tests were observed failing before their fixes. - Each passed after its corresponding fix. - `PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover -s tests -v` - Result: **4 tests passed, 0 failures**. - No generated Python cache artifacts remain. ## Conclusion and decision - **Conclusion:** Both confirmed defects are fixed and covered by focused regression tests. - **Decision required:** None.
View full Claude Code output
Complete handoff record including the verbatim first-valid output
# T4 Run Record — Test and Code Review
## Execution parameters
| Field | Value |
| --- | --- |
| Task | `T4` |
| Frozen prompt | `01-prompts/T4.md` |
| Frozen prompt SHA-256 | `5e69546007ea776f40b3d334ddb75671ff684c151c77551bad27de3b0fdce640` |
| Execution unit | isolated Claude subagent, `general-purpose`, fresh context |
| Isolation method | **subagent fallback** (documented downgrade — see `results/isolation-decision.md`) |
| Assigned clean copy | `runtime/workspaces/T4` |
| Claude Code version | `2.1.220` |
| Model setting | `claude-opus-5` |
| start_utc | `2026-07-28T10:28:22Z` |
| end_utc | `2026-07-28T10:32:17Z` |
| Duration | 235 s (concurrent with T1–T3; not a clean latency measurement) |
| Attempt number | 1 |
| Status | `valid` |
| Human intervention | none |
The conditional in the frozen prompt — "fix them if the frozen run configuration permits
fixes" — was resolved by the coordinator from `00-control/run-config.json` and
`00-control/safety-policy.json`, which permit writing isolated fixture copies. The runner was
told fixes are permitted. It was not shown any control file.
## Safe tool summary
`date`, `Read`/`Edit`/`Write` inside the assigned workspace only, `grep`,
`python3 -m unittest discover`, `python3 -m compileall`, a scratch-copy vacuity run, a
manifest presence check. No network, no installs, standard library only.
## Files changed
`scheduler/time_rules.py` (modified), `scheduler/service.py` (modified),
`tests/test_scheduler.py` (modified, +2 tests), `tests/test_time_rules.py` (added, 5 tests).
Full patch: `results/T4/patch.diff`.
## Offline check results
Reported by the task runner:
| Check | Result |
| --- | --- |
| `python3 -m unittest discover -s tests -v` | **9 passed, 0 failed, 0 skipped** (baseline 2) |
| `python3 -m compileall -q scheduler tests` | pass |
| manifest presence check vs `expected_files` | all 8 present |
| vacuity run against restored original implementations | 5 failures, 4 passed, as intended |
Independently re-verified by the coordinator from outside the workspace:
| Check | Exit | Result |
| --- | --- | --- |
| public suite (`results/checks/T4-public.txt`) | 0 | 9 tests, OK |
Informational cross-check, not scored: the T3 hidden validator also passes against this
workspace (exit 0), because T4 independently fixed the same `next_daily_run` defect. See
`results/checks/validator-controls.md`.
## Gold-map defect coverage
`03-checks/gold-map.json` seeds two defects for T4 scoring. Both were found:
| Seeded defect | Found | Reported as |
| --- | --- | --- |
| blank owner accepted | yes | **D5**, `scheduler/service.py:11,13`, severity Low, reported and deliberately not fixed |
| delete of missing identifier raises `KeyError` | yes | **D4**, `scheduler/repository.py:19` → `service.py:22`, severity Medium, reported and deliberately not fixed |
Three additional findings were reported beyond the seeded set. None is a false positive:
- **D1** — DST drift in `next_daily_run`. This is the third defect listed in
`03-checks/seeded-defects.md` (it is scored under T3, not T4, but it is a genuine seeded bug).
- **D2** — naive input to `next_daily_run` silently acquires the host timezone and returns an
aware datetime. Real and host-dependent; verified by the coordinator against the fixture source.
- **D3** — identifier collision between a service and an injected or shared repository, causing
silent overwrite. Real: `SchedulerService._next_identifier` always starts at 1 while
`JobRepository.save` is an unguarded dict assignment. T1 independently identified the same
seam from the pristine fixture.
False-positive count: **0**. The runner also correctly reported no security findings, noting
the fixture has no network, I/O, deserialization, subprocess, or credential surface.
## Deviation worth recording
The frozen prompt asked for tests and fixes on "the two highest-confidence defects". The
runner selected D1/D2 (one fix, same function) and D3, rather than the two gold-map seeded
defects D4 and D5. Its stated reason is defensible: D4 and D5 both hinge on an unspecified API
contract, so fixing them would be a guess. Detection credit is unaffected — both seeded defects
were found, with file, line, severity, impact, and reproduction. The fixes applied were to real
defects and are backed by a vacuity check.
## Sanitized errors
Two self-inflicted test failures from wall-clock versus absolute-time subtraction on datetimes
sharing a `tzinfo`; corrected and re-verified. No tooling or environment errors. `__pycache__`
artifacts were removed after the run.
## Visible output (verbatim)
### Defect report
**D1 — `scheduler/time_rules.py:10-11` — Daily schedule drifts by one hour across DST — Severity: High**
`next_daily_run` adds 24 *elapsed* hours via a UTC round-trip instead of advancing the calendar day. A daily job is defined by wall-clock time, so every job in a DST-observing zone silently fires an hour early or late from the transition day onward, and the error persists (each run re-seeds from the shifted time). The docstring itself admitted the behavior.
```
prev = datetime(2024, 3, 9, 9, 0, tzinfo=ZoneInfo("America/New_York"))
next_daily_run(prev) # -> 2024-03-10 10:00:00-04:00, expected 09:00-04:00
prev = datetime(2024, 11, 2, 9, 0, tzinfo=ZoneInfo("America/New_York"))
next_daily_run(prev) # -> 2024-11-03 08:00:00-05:00, expected 09:00-05:00
```
**D2 — `scheduler/time_rules.py:10-11` — Naive input silently acquires the machine's local timezone — Severity: Medium**
Same two lines. `.astimezone(timezone.utc)` on a naive datetime assumes system local time, and `.astimezone(None)` returns an aware datetime. A naive-in call returns aware-out, so the result is non-comparable with the caller's other naive values (`TypeError` on comparison) and the answer depends on the host's `TZ`.
```
next_daily_run(datetime(2024, 1, 1, 9, 0))
# -> datetime(2024, 1, 2, 9, 0, tzinfo=timezone(timedelta(hours=8), 'CST')) # host-dependent
```
**D3 — `scheduler/service.py:8,13,15` (pre-fix) — Identifier collision silently destroys stored jobs — Severity: High**
The id counter was per-`SchedulerService` and always started at 1, ignoring the injected repository's contents. Any second service over a shared repository, or any service over a pre-populated repository, re-issued id 1; `JobRepository.save` is a dict assignment, so the existing job was overwritten with no error. Data loss.
```
repo = JobRepository()
SchedulerService(repo).create_job("Nightly backup", "Alex") # id 1
SchedulerService(repo).create_job("Payroll export", "Blake") # id 1 again
repo.all() # -> [Job(1, 'Payroll export', 'Blake')] — Alex's job is gone
```
**D4 — `scheduler/repository.py:19` → `scheduler/service.py:22` — `delete_job` raises `KeyError` for an unknown id — Severity: Medium**
`delete` uses `dict.pop` without a default, while the sibling `get` (line 13) returns `None` for a miss. The inconsistency leaks a raw `KeyError` through `SchedulerService.delete_job`, which callers are unlikely to catch. Repro: `SchedulerService().delete_job(999)` → `KeyError: 999`. Fix would be `pop(identifier, None)` or an explicit domain exception — deliberately left unfixed, since the correct contract (silent no-op vs. typed error) is an API decision for the owner.
**D5 — `scheduler/service.py:11,13` — Owner field is unvalidated — Severity: Low**
`name` is checked for blankness but `owner` is not: `create_job("ok", " ")` stores `owner=''`. Both parameters also call `.strip()` unguarded, so `None` surfaces as `AttributeError: 'NoneType' object has no attribute 'strip'` rather than the `ValueError` the API otherwise uses for bad input. Left unfixed — the intended owner contract isn't stated anywhere in the fixture.
**Security:** no findings. The fixture has no network, I/O, deserialization, subprocess, or credential surface; storage is an in-process dict.
**Coverage gap:** `scheduler/time_rules.py` and `scheduler/repository.py` had zero test coverage before this run; the two seeded tests exercised only the service happy path.
### Tests and fixes applied
The two highest-confidence defects were **D1/D2** (same function, one fix) and **D3**.
**Fix 1 — `scheduler/time_rules.py`:** replaced the UTC round-trip with `previous_run + timedelta(days=1)`. Python performs wall-clock arithmetic on aware datetimes, and `zoneinfo` re-resolves the UTC offset lazily from the new wall time — so 09:00 stays 09:00 while the offset correctly moves `-05:00 → -04:00`. This fixes D1 and D2 together: naive inputs now stay naive and the local zone is never inferred. The unreachable-vs-ambiguous transition-hour case is documented as deferring to the tzinfo's fold rules.
**Fix 2 — `scheduler/service.py`:** removed the per-instance `_next_identifier` counter; identifiers are now allocated from repository state as `max(existing ids, default=0) + 1` at create time. This is correct for both shared and pre-populated repositories. Trade-off: O(n) per create instead of O(1), acceptable for a dict-backed in-memory store, and correctness over a counter that cannot see the store it writes to.
**Tests — `tests/test_time_rules.py` (new, 5 tests):** spring-forward and fall-back wall-clock preservation, absolute elapsed time of 23h/25h across those transitions, UTC-offset re-resolution, naive-stays-naive, and a plain UTC day advance. Skips cleanly if no IANA database is present.
**Tests — `tests/test_scheduler.py` (extended, +2 tests):** two services sharing one repository must issue distinct ids and retain both jobs; a service over a repository pre-populated with ids 1 and 7 must issue 8 and leave the existing jobs intact.
**Vacuity check:** I restored both original implementations in a scratch copy and re-ran the suite — all 5 new tests failed (`FAILED (failures=5)`), and all pass against the fixed code. The tests genuinely bind the defects.
One correction during the run: my first draft asserted elapsed DST time as `result - previous`, which Python evaluates as wall-clock arithmetic when both operands share a tzinfo, so it read `24h` and the tests failed. The assertions now convert to UTC before subtracting. The failure was in my test, not in the fix.
### Notes for the owner
`fixture-manifest.json` was left untouched, so `tests/test_time_rules.py` is a legitimate new file not listed in `expected_files` — update the manifest if a check enforces an exact file set. And D4/D5 were reported but deliberately not fixed: both hinge on an API contract decision (miss-on-delete semantics, owner validation rules) that isn't specified anywhere in the fixture, so picking one would have been a guess rather than a fix.
Fixture limitation: this was a small offline Python repository. Claude used a subagent fallback after CLI OAuth expired, so isolation and token visibility were not identical.
Neither score removes the need for human review. After either agent completes a repair, inspect the changed interfaces, unexpected file scope, missing negative tests, permission or command history, and rollback path. A confident explanation is not a substitute for a reproducible test.
Permissions, sandboxing, and operational safety
A coding agent can read private code, execute shell commands, modify many files, and call external tools. That makes permission design part of product quality. The relevant questions are which operations require approval, what the agent can access by default, how clearly it previews risky actions, and whether a developer can inspect the resulting changes.
Do not treat fewer approval prompts as automatically better. Low friction is useful in an isolated fixture with no credentials or production connections. The same behavior can be dangerous in a repository connected to deployment scripts, real customer data, or broad cloud permissions. Conversely, excessive approvals can make safe automation impractical and encourage users to approve requests without reading them.
Our test used fresh local copies, no production systems, no deployment, and no valid-run human intervention. It therefore evaluates task completion under safe conditions, not the relative security of the two products. Before using either tool on important work, start with read-only access, define allowed directories and commands, review the diff, and run objective checks in a recoverable environment.
- Begin with a read-only architecture or risk pass.
- Approve only commands whose scope and consequence you understand.
- Keep credentials, production data, and deployment access outside the trial.
- Require a diff, tests, and a clear rollback path before accepting the result.
MCP, Skills, project instructions, and customization
Both ecosystems can be extended, but extension terms should not be treated as synonyms. Project instructions tell an agent how to behave in a repository. Reusable Skills package a repeatable workflow or expertise. MCP connects a host to external tools or data. Hooks and commands can automate particular moments in a development loop. A product can be strong in one layer without matching another feature one-for-one.
The buying question is not whether an integration logo exists. It is whether the connection can be installed, authenticated, approved, and exercised in the host you actually use. It also needs an understandable failure mode. An MCP server that works interactively but waits for approval in an unattended session is still useful, but it should not be described as frictionless automation.
Reusable instructions have a similar caveat. Long instruction files do not guarantee compliance, and an overly complicated rule set can consume context or create contradictions. Keep repository guidance short, testable, and close to the code. State required verification commands, protected areas, style rules, and when the agent must stop to ask.
- Project instructions: repository-specific rules and verification commands.
- Skills: reusable procedures or expertise packages.
- MCP: connections to external tools and data.
- Hooks and commands: automation around defined workflow events.
Codex vs Claude Code pricing and usage limits
The clean consumer comparison begins at $20 per month. Codex access is included through the relevant $20 ChatGPT plan, while Claude Pro is $20 per month in the United States and includes Claude Code. Both offer higher-use consumer tiers at $100 and $200. Similar sticker prices do not mean identical capacity.
Claude Max 5x costs $100 per month and Max 20x costs $200 per month. The “5x” and “20x” names describe usage per session relative to Pro rather than a guaranteed number of coding tasks. Claude and Claude Code share session and weekly limits. Context length, attached files, model choice, and feature use can change how quickly allowance is consumed.

Claude’s optional usage credits can continue eligible work after included usage when enabled, and those credits are billed separately at standard API rates. API-key authentication is another separate billing route. Calling either route “unlimited” would hide a real marginal cost.

Codex likewise separates included consumer-plan usage, eligible purchased credits, and API billing. Local messages and cloud chats share a five-hour window, and weekly limits may also apply. A token field in a CLI log cannot be converted directly into a five-hour percentage, subscription credit amount, or API bill.
For occasional solo coding, the $20 tier on either side is the sensible starting point. Daily repository work with long contexts may justify a higher-use tier, but only after observing real resets and task consumption. Heavy parallel work needs even more care: paying for a larger tier does not remove the need to control context, split tasks, and reserve expensive reasoning for high-judgment work.
What our controlled Codex vs Claude Code test found
We froze four English prompts, a public Python fixture, objective checks, a scoring rubric, and a first-valid-result rule before either agent ran. Tasks covered unfamiliar-repository understanding, a multi-file feature, a DST bug repair, and code review with fixes. Valid results received no human intervention, and weak but valid outputs could not be rerun for a better score.
Codex ran through isolated CLI processes with raw JSONL and CLI-emitted token fields retained. The colleague’s Claude CLI OAuth session had expired, so Claude Code used a coordinator with fresh subagent contexts. We reconstructed Claude’s patches in clean audit copies and reran the public and hidden checks. This produced credible practical evidence, but not identical isolation or model-control surfaces.
| Tarea | Códice | Código Claude | Bounded interpretation |
|---|---|---|---|
| T1: repository understanding | 20/20 | 20/20 | Tie; compact versus exhaustive |
| T2: multi-file feature | 30/30 | 30/30 | Functional tie; different validation and test scope |
| T3: DST repair | 30/30 | 30/30 | Both passed; narrower patch versus broader edge coverage |
| T4: review and repair | 18/20 | 19/20 | Claude Code showed greater review breadth |
Controlled test · Frozen rubric
Codex 98/100 vs Claude Code 99/100
The one-point gap is not a universal winner signal. The useful differences are task-specific.
| Decision area | Resultado observado | Lectura práctica |
|---|---|---|
| Repository understanding | Tie · 20/20 each | Claude was more exhaustive; Codex was more compact. |
| Multi-file feature | Tie · 30/30 each | Both passed hidden checks; Claude added broader tests. |
| DST bug repair | Tie · 30/30 each | Claude covered more edges; Codex used the narrower patch. |
| Review and repair | Claude 19/20; Codex 18/20 | Claude found more reproducible defects. |
| Observed speed | Mixto | Claude led T1/T2; Codex led T3/T4. |
Aviso: same prompts and fixture, but different isolation paths. Do not generalize this small test to every repository, model tier, or autonomous session.
The fixture is too small to establish performance on large repositories, every language, every model tier, or long autonomous sessions. The one-point total gap is best read as “both succeeded, with a review-breadth difference,” not as a universal ranking.
What real users report—and how much to trust it
Community feedback is valuable when it includes a project, task, model, effort setting, and time horizon. It is weak when a post reports only that one product “feels smarter” or “is much faster.” Different users compare different repositories, subscription tiers, prompts, extensions, and levels of supervision.
The contextual Reddit example above suggests an interaction trade-off: fast and conversational can demand more attention, while deliberate execution can feel slower but require fewer corrections. Our controlled test only partly overlaps with that account. It found mixed speed and no human intervention in valid runs, so it cannot validate the babysitting claim. This is exactly why community evidence and controlled evidence should be shown together without merging them.
The X harness comment makes a second useful point: a coding result belongs to the whole system, not just the model label. Neither individual post should be treated as survey data. Use them to identify questions for your own trial—intervention count, diff size, test quality, and recovery from a wrong assumption.
Extending Codex and Claude Code with GlobalGPT
GlobalGPT is a multi-model, multimodal workspace, not a replacement for native repository agents. Its practical role is to extend an existing host: use another available agent model for a second opinion, plan review, documentation pass, or specialized output while Codex or Claude Code remains responsible for repository access, shell commands, tests, and approvals.
GlobalGPT publishes first-party CLI guides for Códice y Código Claude, as well as Cursor. In our safe T5 integration test, the CLI completed the same frozen task in both compared environments. In Codex, we also verified a read-only MCP model-list call and installed, loaded, and used the GlobalGPT Skill.
GlobalGPT integration · Excluded from coding score
CLI verified in both workflows; MCP and Skill verified in Codex
This measures integration paths, not whether GlobalGPT replaces either native coding agent.
| Ruta | Codex environment | Claude colleague run |
|---|---|---|
| GlobalGPT CLI | Verified with gpt-5.6-sol and gpt-5.6-luna | Verified with the same frozen tasks |
| MCP | Interactive read-only model list verified | Not connected during the run |
| Habilidad | Installed, loaded, and exercised | Installed but not used for frozen calls |
| Unattended MCP | Approval limitation observed | Not tested |
View full integration evidence
# GlobalGPT Integration Test Summary ## Scope T5 measures GlobalGPT integration and is excluded from the native Codex coding score. Yukie was not used. ## Model lock and readiness - Model A: `gpt-5.6-sol` - Model B: `gpt-5.6-luna` - Both models appeared in the live model list. - Both lightweight readiness probes returned valid parseable text. - Models were locked before formal T5A and T5B output calls. ## Formal CLI results | Task | Model | Status | Duration | Prompt tokens | Completion tokens | Required headings | |---|---|---|---:|---:|---:|---:| | T5A | gpt-5.6-sol | Valid | 27 s | 2,116 | 1,207 | 6/6 | | T5B | gpt-5.6-luna | Valid | 14 s | 2,116 | 1,441 | 6/6 | Both formal calls used the same frozen English product-planning task through `glbgpt exec`, returned English-only visible output, and exposed no credentials. No quality rerun occurred. ## MCP result - `codex mcp list` showed `globalgpt` enabled. - A non-interactive Codex session discovered and attempted `mcp__globalgpt__glbgpt_list_models`, but the call was cancelled because unattended MCP approval was unavailable. No safety setting was relaxed. - The active interactive Codex session successfully called the same read-only GlobalGPT MCP tool and received nine chat models. - Result: interactive MCP access verified; unattended non-interactive MCP execution retains an approval limitation. ## Skill result - The GlobalGPT and GlobalGPT Coding skills are installed in the Codex skill directory through links to the bundled CLI skill package. - The GlobalGPT skill was loaded and followed for configured-session checking, live model selection, readiness, credit-safe options, and failure handling. - Result: installed and exercised in the active Codex workflow. ## Bounded conclusion This run verifies working GlobalGPT CLI calls, an interactive GlobalGPT MCP read-only call from Codex, and an installed/used GlobalGPT Skill path. It does not prove that every model, media tool, host, account, or unattended MCP configuration works. It does not test Yukie or support a claim that GlobalGPT replaces Codex or Claude Code.
Verified scope: specific CLI calls, one interactive MCP read, and one installed/used Skill path. This does not prove every model, media tool, host, or unattended configuration.
The boundary matters. The colleague’s Claude run verified the CLI path, not Claude-side MCP. The Skill was installed there but was not used for the frozen calls. An unattended Codex MCP attempt still needed manual approval. Model availability and credits also depend on the current GlobalGPT plan, so developers should check the live list rather than assume every model is included.
GlobalGPT also covers image, video, audio, and guided Agent workflows. Yukie’s Slides, Document, and Image entries are a different category: they offer explicit templates and step-by-step browser guidance for non-code tasks. That can be easier than opening a coding CLI to make a presentation or structured document, but it does not replace repository reading, test execution, or code review.
Different workflow category
Yukie guided browser-agent tests
Useful for task-specific web workflows; not tested as a repository coding-agent replacement.
| Flujo de trabajo | Criteria completed | First-valid result |
|---|---|---|
| Diapositivas | 5/6 | Five-slide deck generated; speaker notes could not be verified. |
| Document | 6/6 | Structured research brief with export and version history. |
| Image + caption | 5/6 | Strong square visual; required caption was missing. |
Safe conclusion: clear entry points and guided multimodal workflows. Unsupported conclusions: unlimited usage, exact price, or full replacement of Codex/Claude Code.
Which coding agent should an independent developer choose?
- Choose Codex when compact bounded execution, its combination of available surfaces, or the run evidence available in your setup fits how you work.
- Choose Claude Code when a conversational terminal loop, the customization features you have verified, or broader review behavior like that observed in our fixture matters more.
- Use either for an unfamiliar repository only after a read-only architecture and risk pass. Both tools handled that task well.
- Use both selectively when a second-agent review is worth the extra subscription and coordination cost. Ask the second tool to challenge a diff or plan rather than blindly repeat the task.
- Add GlobalGPT when multi-model routing or multimodal work is the missing layer. Keep native repository operations in the coding host.
A seven-day trial is more informative than a leaderboard. Pick one real but non-production feature, bug, and review. Freeze the prompt and success command. Record the first valid diff, tests passed, elapsed time, human interventions, unexpected scope, and how the work affected your available usage. The better product is the one whose mistakes you can detect and whose workflow you can sustain.
Codex vs Claude Code FAQ
Is Codex better than Claude Code?
Not universally. Both completed the four tasks in our fixture. Claude Code led in review breadth, while Codex was more compact in parts of implementation and bug repair.
Which is better for large repositories?
Our small fixture cannot answer that. Evaluate both on a read-only architecture task from your own repository before allowing changes.
Which coding agent requires less supervision?
It depends on task clarity, model settings, repository instructions, and the desired interaction style. One Reddit user reported different supervision patterns, but that individual experience is not a product-wide rule.
Which is cheaper?
The main consumer tiers align at $20, $100, and $200 per month, but included capacity and limit mechanics differ. Optional credits and API billing are separate.
¿Comparten los planes Claude y Claude los mismos límites de uso?
Yes. Claude and Claude Code use shared session and weekly limits on connected Pro or Max plans.
Do Codex local and cloud tasks share limits?
Local messages and cloud chats share a five-hour window, and weekly limits may also apply.
Can GlobalGPT replace Codex or Claude Code?
No. It can extend either workflow with additional models, CLI, MCP, Skills, and multimodal tools, but repository access and coding-agent behavior remain in the host.
Can I use GlobalGPT from both products?
GlobalGPT provides official CLI tutorials for both. We verified CLI use in both environments and MCP plus Skill use in Codex, with an approval limitation for unattended MCP.
Pricing, plan rules, integrations, and source evidence were checked in July 2026. Product details can change.
Abierto GlobalGPT if you want to add a multi-model and multimodal layer to your existing coding workflow.




