An AI coding task can leave an intended file on disk without making it visible in ordinary Git status. If a broad ignore pattern hides a migration or configuration example, a review based only on the files Git lists can miss part of the handoff.
Make the expected deliverables explicit, then distinguish indexed, ignored, untracked and missing files. This guide provides a disposable Git drill that reproduces those states and identifies the ignore rule. It does not run a coding assistant or inspect your real repository.
Know what you expected to receive
Orbit Desk is a fictional business reviewing an authored coding-task handoff. Its expected list contains app.py, migrations/add booking.sql, notes.md and README.md. The SQL file is just a commented fixture, not a real database migration. The demo writes all files inside a new temporary repository and never executes their contents.
| Expected fixture | Observed state | What still needs review |
|---|---|---|
| app.py | indexed | Staged contents and intended patch |
| migrations/add booking.sql | ignored | Why *.sql hides an intended artifact |
| notes.md | untracked | Whether it belongs in the handoff |
| README.md | missing | Whether the task omitted a deliverable |
The expected list is part of the task contract. It is not reconstructed from whatever ordinary status happens to show. If the list itself omits a required artifact, this drill cannot discover that requirement for you.
Status is a view, not the full task contract
The status manual describes tracked changes and untracked-file reporting. The drill explicitly requests all untracked files in short output, yet its ignored migration still does not appear. This is different from claiming that the repository is clean: app.py and .gitignore are staged in the example.
The ignore manual explains that ignore patterns concern intentionally untracked files and do not affect files already tracked by Git. The fixture stages app.py before adding a *.py rule, then confirms it remains indexed. Ignore rules are useful for generated assets and local configuration; an unexpected omission is a reason to inspect the rule, not to disable the ignore system.
The check-ignore manual documents verbose rule output and exit statuses: zero means an ignored path was found, one means none was ignored, and 128 is a fatal error. The script accepts the expected zero/one distinction and stops on other command failures. It uses verbose output to identify the migration’s *.sql rule.
Run the isolated reproduction
Prerequisites: Python 3.14.3 and Git on PATH were used on Windows. The executed Git version was 2.51.0.windows.1. No installation, repository clone, commit, push, model request or paid service was required. The upstream v2.51.0 implementation was inspected as a versioned reference; the installed binary includes Windows-specific packaging.
Save this as handoff_drill.py in a new disposable directory. It strips inherited Git-specific environment overrides for its child commands, uses an empty global configuration and ignore file, and initializes with an empty template. Those choices make the fixture reproducible without borrowing the user’s real ignore settings. The temporary repository is removed at the end. The optional evidence file is written only to the new path you supply.
"""Run a Git ignore handoff drill in a new disposable repository."""
from pathlib import Path
import hashlib
import json
import os
import shutil
import subprocess
import sys
import tempfile
def drill():
if not __debug__:
raise RuntimeError('Run without Python optimization for drill assertions')
executable = shutil.which('git')
if not executable:
raise RuntimeError('Git must already be installed and on PATH')
logs = []
with tempfile.TemporaryDirectory(prefix='orbit-git-handoff-') as folder:
base = Path(folder)
repo, template = base/'repo', base/'empty-template'
repo.mkdir()
template.mkdir()
empty_config = base/'empty-config'
empty_config.write_text('', encoding='utf-8')
env = {k: v for k, v in os.environ.items() if not k.upper().startswith('GIT_')}
env.update(GIT_CONFIG_NOSYSTEM='1', GIT_CONFIG_GLOBAL=str(empty_config))
def git(args, allowed=(0,)):
literal = ['--literal-pathspecs'] if args[0] == 'ls-files' else []
result = subprocess.run([
executable, *literal, '-c', 'core.excludesFile=' + str(empty_config),
*map(str, args)], cwd=repo, env=env, capture_output=True, text=True, timeout=30)
logs.append({'args': list(map(str, args)), 'returncode': result.returncode,
'stdout': result.stdout, 'stderr': result.stderr})
if result.returncode not in allowed:
raise RuntimeError('Git command failed: ' + result.stderr)
return result
version = git(['--version']).stdout.strip()
git(['init', '--quiet', '--template=' + str(template)])
(repo/'app.py').write_text('VALUE = 1\n', encoding='utf-8')
git(['add', '--', 'app.py'])
(repo/'.gitignore').write_text('*.sql\n*.py\n', encoding='utf-8')
git(['add', '--', '.gitignore'])
(repo/'migrations').mkdir()
migration = repo/'migrations'/'add booking.sql'
migration.write_text('-- authored fixture, not a real migration\n', encoding='utf-8')
(repo/'notes.md').write_text('Authored handoff note\n', encoding='utf-8')
def classify(relative):
rel = Path(relative)
if not relative or rel.is_absolute() or rel.drive or any(
part == '..' or part.casefold() == '.git' for part in rel.parts):
raise ValueError('Use an ordinary repository-relative file path')
target = (repo/rel).resolve()
if not target.is_relative_to(repo.resolve()):
raise ValueError('Path leaves the disposable repository')
if not target.exists():
return 'missing'
if not target.is_file():
raise ValueError('Expected a file, not a directory')
name = rel.as_posix()
if git(['ls-files', '--error-unmatch', '--', name], (0, 1)).returncode == 0:
return 'indexed'
ignored = git(['check-ignore', '--quiet', '--', './' + name], (0, 1))
return 'ignored' if ignored.returncode == 0 else 'untracked'
default = git(['status', '--short', '--untracked-files=all']).stdout
assert 'notes.md' in default and 'add booking.sql' not in default
checks = ['ordinary status omits ignored migration but shows untracked note']
index_before = hashlib.sha256((repo/'.git/index').read_bytes()).hexdigest()
source_before = hashlib.sha256(migration.read_bytes()).hexdigest()
expected = ['app.py', 'migrations/add booking.sql', 'notes.md', 'README.md']
states = {name: classify(name) for name in expected}
assert list(states.values()) == ['indexed', 'ignored', 'untracked', 'missing']
checks.append('four expected-file states confirmed, including filename with space')
rule = git(['check-ignore', '--verbose', '--', 'migrations/add booking.sql']).stdout
assert '*.sql' in rule and '.gitignore' in rule
checks.append('verbose check identifies the SQL ignore rule')
assert git(['check-ignore', '--quiet', '--', 'app.py'], (0, 1)).returncode == 1
checks.append('indexed Python file remains outside ignore handling')
for bad in ('../outside.txt', '.git/config', str(base/'absolute.txt')):
try:
classify(bad)
except ValueError:
pass
else:
raise AssertionError('Unsafe expected path accepted')
checks.append('parent, metadata and absolute paths rejected')
try:
classify('migrations')
except ValueError:
pass
else:
raise AssertionError('Directory accepted as file')
checks.append('directory rejected as an expected file')
assert hashlib.sha256((repo/'.git/index').read_bytes()).hexdigest() == index_before
assert hashlib.sha256(migration.read_bytes()).hexdigest() == source_before
checks.append('classification preserves index and migration bytes')
return {'passed': True, 'git': version, 'states': states, 'checks': checks,
'python': sys.version, 'logs': logs}
if __name__ == '__main__':
evidence = None
if len(sys.argv) == 3 and sys.argv[1] == '--evidence':
evidence = Path(sys.argv[2])
if evidence.exists():
raise FileExistsError('Choose a new evidence file')
elif len(sys.argv) != 1:
raise SystemExit('Usage: python handoff_drill.py [--evidence NEW.json]')
result = drill()
if evidence is not None:
with evidence.open('x', encoding='utf-8') as handle:
json.dump(result, handle, indent=2)
print(json.dumps({k: result[k] for k in ('passed', 'git', 'states', 'checks')}, indent=2))
Run the command below. Choose a fresh evidence filename for another run; the script refuses to overwrite one. The saved JSON contains the actual Git arguments, exit codes and output from the drill. Python optimization must remain disabled so the drill assertions execute.
python -X utf8 handoff_drill.py --evidence git-evidence.json
The executed summary was:
{
"passed": true,
"git": "git version 2.51.0.windows.1",
"states": {
"app.py": "indexed",
"migrations/add booking.sql": "ignored",
"notes.md": "untracked",
"README.md": "missing"
},
"checks": [
"ordinary status omits ignored migration but shows untracked note",
"four expected-file states confirmed, including filename with space",
"verbose check identifies the SQL ignore rule",
"indexed Python file remains outside ignore handling",
"parent, metadata and absolute paths rejected",
"directory rejected as an expected file",
"classification preserves index and migration bytes"
]
}
Turn the result into a better assistant handoff
For a Codex, Claude or Antigravity task, agree on the expected deliverables alongside the requested behavior and tests. Ask the handoff to explain which intended artifacts are indexed, untracked, ignored or absent. Then inspect the actual diff and test the behavior. No assistant host was exercised in this drill; this is a review pattern you can apply around one.
The code intentionally operates only on its disposable fixture. It is not a general repository scanner. In a real repository, inspect the actual project, per-directory, local and user-level ignore configuration rather than copying the fixture’s empty-config isolation. A hidden item could be correctly excluded private configuration or generated output. Review its contents and purpose before deciding whether it belongs in source control.
Do not automatically force-add every ignored file. If a legitimate source artifact is hidden by an overbroad pattern, review a narrowly scoped rule correction with the project owner. If it should stay local, document how a clean checkout recreates or supplies it. Indexed state alone is not proof of a correct patch, inclusion in a commit, secret-free contents or deployment readiness.
Use this alongside an isolated coding-task worktree and the runtime-plus-static review example. File presence and code behavior answer different questions.
What the evidence establishes
Seven actual checks passed: ordinary status behavior, all four expected-file states including a filename with a space, verbose rule diagnosis, indexed-file behavior despite a matching rule, rejected parent/metadata/absolute paths, directory rejection and unchanged index/migration bytes during classification. The final article code was extracted and run in a separate disposable directory. Public code is compared with independent preserved hashes and reproduced after publication.
This is a small Windows Git/Python fixture, not a security boundary for hostile repositories. Submodules, linked worktrees, concurrent filesystem changes, every filename encoding, alternate index layouts and production repository policies were not validated. No real application migration, assistant session, CI service, remote or business dataset was exercised. Keep normal patch, secret, test and release review in place.
Jason Ead publishes AlgoThoughts AI News and connects practical engineering with My Biz Heroes: websites, automation, workflows, AI tools and training. Explore Jason’s work. The chameleon cover is generated conceptual artwork; the interior table reports actual fixture states.
