{"record":{"id":"39727277c8caea12","repo":"nikivdev/code","slug":"refusing-to-commit-internal-ai-files","errorCode":null,"errorMessage":"Refusing to commit internal .ai files","messagePattern":"Refusing to commit internal \\.ai files","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/commit.rs","lineNumber":6612,"sourceCode":"    setup::add_gitignore_entry(repo_root, \".ai/internal/\")?;\n    Ok(())\n}\n\nfn ensure_no_internal_staged(repo_root: &Path) -> Result<()> {\n    if env::var(\"FLOW_ALLOW_INTERNAL_COMMIT\").as_deref() == Ok(\"1\") {\n        return Ok(());\n    }\n    let staged = internal_staged_paths(repo_root);\n    if staged.is_empty() {\n        return Ok(());\n    }\n\n    println!(\"Refusing to commit internal .ai files:\");\n    for path in staged {\n        println!(\"  - {}\", path);\n    }\n    println!(\"Remove these from staging or set FLOW_ALLOW_INTERNAL_COMMIT=1 to override.\");\n    bail!(\"Refusing to commit internal .ai files\");\n}\n\nfn internal_staged_paths(repo_root: &Path) -> Vec<String> {\n    let output = Command::new(\"git\")\n        .args([\"diff\", \"--cached\", \"--name-only\"])\n        .current_dir(repo_root)\n        .output();\n\n    let Ok(output) = output else {\n        return Vec::new();\n    };\n    if !output.status.success() {\n        return Vec::new();\n    }\n\n    let files = String::from_utf8_lossy(&output.stdout);\n    files\n        .lines()","sourceCodeStart":6594,"sourceCodeEnd":6630,"githubUrl":"https://github.com/nikivdev/code/blob/a747e741ae92c09071d0ae946ab48488adcff1ce/src/commit.rs#L6594-L6630","documentation":"Before committing, the tool inspects staged files (git diff --cached --name-only) and refuses to commit if any internal .ai working files are staged. These are tool-internal files that should not enter project history. The error lists the offending paths and can be bypassed with FLOW_ALLOW_INTERNAL_COMMIT=1.","triggerScenarios":"Running `f commit` while internal .ai files (e.g. commit queue/review state under .ai/) are in the git staging area, as detected by internal_staged_paths().","commonSituations":"User ran `git add .` or `git add -A` after the tool wrote its internal state files, .gitignore is missing entries for the .ai directory, or a previous crashed run left state files behind that then got staged.","solutions":["Unstage the listed files: `git restore --staged <path>` (or `git rm --cached -r .ai/`)","Add the .ai directory to .gitignore so it never gets staged","Run `git add` with explicit paths instead of `git add .`","Set FLOW_ALLOW_INTERNAL_COMMIT=1 to deliberately override, only if you truly want these files committed"],"exampleFix":"// before (shell)\ngit add . && f commit -m \"msg\"   # stages .ai/* too\n// after (shell)\nprintf '.ai/\\n' >> .gitignore && git restore --staged .ai && f commit -m \"msg\"","handlingStrategy":"validation","validationCode":"let staged = Command::new(\"git\")\n    .args([\"diff\", \"--cached\", \"--name-only\"])\n    .output()?;\nlet names = String::from_utf8_lossy(&staged.stdout);\nlet internal: Vec<&str> = names.lines()\n    .filter(|p| p.starts_with(\".ai/\")).collect();\nif !internal.is_empty() {\n    eprintln!(\"unstage first: {}\", internal.join(\", \"));\n}","typeGuard":null,"tryCatchPattern":"if let Err(e) = tool.commit(msg) {\n    if e.to_string().contains(\"internal .ai files\") {\n        let _ = Command::new(\"git\").args([\"restore\", \"--staged\", \".ai\"]).status();\n        tool.commit(msg)?;\n    } else { return Err(e.into()); }\n}","preventionTips":["Never use `git add .`; stage explicit paths","Keep .ai/ in .gitignore from repo setup","Review `git status` before running commit tooling","Only set FLOW_ALLOW_INTERNAL_COMMIT=1 deliberately and temporarily"],"tags":["git","guardrail","staging","dotfiles"],"backgroundTag":"protected-files-staged","analyzedSha":"a747e741ae92c09071d0ae946ab48488adcff1ce","analyzedAt":"2026-09-01T22:43:55.719Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}