PorkiCoder Research · Bash SFT series

Bash SFT note 03: file search reached 92/98, but more data did not help

30 August 2026 · Session 3 report · research note, not a paper · follows note 02

We ran two experiments on the same 1.5-billion-parameter coding model. The first asked what kind of training examples help it imitate Bash answers. The second asked whether its file-search commands actually work on macOS. The best file-search checkpoint passed 92 of 98 held-back tasks on its first try. Adding more unverified traces or repeating the hardest training families did not improve that result. In a separate matched-prompt comparison, that checkpoint passed 90 tasks, its exact stock base passed 8, and a small natural-language-to-shell specialist passed 4.

How to read this report

The two experiments share a base model, but they do not share a scoreboard. Think of them as a written exam and a driving test:

So 0.7180 and 92/98 are answers to different questions. They cannot be combined into one ranking. For the product decision, 92/98 is the relevant result.

0.7180general Bash imitation: lowest validation loss, from the full 75/15/10 training mix
92/98file-search: tasks passed by the kept checkpoint on one attempt per hidden macOS task
90 · 8 · 4matched prompt: kept checkpoint · exact stock base · small shell specialist, out of 98

What improved. In the general imitation study, removing trajectories made validation loss worse, so those multi-step examples carried useful signal. In the file-search study, one extra low-learning-rate epoch moved the model from 88/98 to 91/98. A later GFR-only epoch with stronger weight decay added one more pass, reaching 92/98, and increased exact matches from 82/98 to 86/98.

What did not help. Adding 1,201 file-search-looking traces that had not been execution-checked left the score at 91/98. Tripling the executed examples from the hardest families also failed to improve the checkpoint it started from. A cosine-with-restarts schedule fell to 80/98, and a follow-up recovery reached only 87/98.

How far the result reaches. The 98 tasks came from the same generator family as the training data, even though they were locked away from training. The historical real-world handoff canary still failed. Mix A’s validation loss is not an executable file-search score. The matched baselines make the in-domain training gain easier to interpret, but they do not turn this development set into evidence of broad real-world transfer.

Scale mix A on many Adas, or test in real life first?

Test the 92/98 checkpoint in real life first. More GPU time is justified only after the model works on searches someone actually needed. When training resumes, add newly executed examples rather than more copies of the same failures: both the unverified harvest data and the hard-family repetition have already failed to improve one-try accuracy.

  1. The product must return one safe, read-only macOS Bash command for a file-search request. The broader imitation study includes other kinds of Bash work, so Mix A’s win does not establish product quality.
  2. The harvest data enlarged the product training mix by 50%, but 91/98 stayed 91/98. Tripling the hard-family examples also failed to beat the simpler GFR-only checkpoint.
  3. Six locked tasks still fail across five families: rg_files_candidates, rg_count, rg_regex_hidden_exclude, jq_json_filter, and find_directories. Three commands return the wrong exit status; three return the wrong text. Real searches in those families will show whether the gap matters in use.
  4. Next, execution-check the 218 harvest candidates still in quarantine. Only then consider renting several Ada GPUs for genuinely different recipes.

What the arm names mean

An “arm” is one version of the training recipe. This experiment changed only the kinds of examples included in supervised fine-tuning (SFT): the model sees a request and a desired answer, then adjusts its weights to make that answer more likely. The base model, number of optimizer steps, and other settings stayed fixed, so the comparison isolates the value of task solutions, repairs, and trajectories.

Shared setup: pinned Qwen/Qwen2.5-Coder-1.5B-Instruct revision, response-only full SFT, 561 optimizer steps, batch 16, max length 1,024, learning rate 2e-5, seed 42, and one 48 GB RTX 6000 Ada.

ArmWhat it trains onRowsVal lossExact % (n=1,030)Teacher-forced loss
A · full 75/15/10~75% task solutions, ~15% error repairs, ~10% trajectories8,9700.718018.10.662
B · no trajectoriesTasks + repairs only; traces dropped8,0780.778717.90.714
C · task onlyFinal answers only6,7700.87519.50.864
D · concrete checksRows whose checks are not the generic task-complete note5,2370.790810.20.734

Task solution = one natural-language request to one command. Error repair = a failed command and the fix. Trajectory = a multi-step trace. Exact % is greedy string match on the 1,030-row episode validation set, not GFR locked execution.

Validation loss for four mix-composition arms Arm A 0.718, Arm B 0.779, Arm D 0.791, Arm C 0.875. Lower is better. 1.00 0.50 0 0.718 0.779 0.875 0.791 A full mix B no traces C task only D checks response-only val loss · matched 561 steps · lower is better

The full mix had the lowest loss on held-back imitation examples. Removing repairs and trajectories produced the largest deterioration.

This chart supports keeping the 75/15/10 diversity for the general corpus. It does not measure whether a file-search command works.

What GFR and harvest mean

GFR means Grok File Retrieval, the product dataset. Each example pairs a natural-language file-search request with one Bash command. Unlike an ordinary text benchmark, the harness actually runs the command on a synthetic directory, then checks its exit status, output, error output, and whether any file changed. The split is 2,402 training tasks, 297 validation tasks, and 98 locked evaluation tasks.

Locked evaluation means those 98 tasks were kept out of the training rows. They were still used as a development scoreboard while recipes were compared. Pass@1 means the model gets one deterministic attempt at each task; 92/98 is 93.9% on that set. “Locked” limits direct training leakage, but it does not make the score an untouched final test or independent of the generator that created the rest of GFR.

Harvest is a 30 August 2026 scrape of 1,201 commands that looked like file search in real Mini coding traces. Those commands were not run through the fixture harness, so their correctness was unknown. Mixing them with the 2,402 GFR rows increased the training set to 3,603 rows but did not raise pass@1.

Episode mix is the separate, older general-Bash corpus of 8,970 rows used for Arms A–D. It measures imitation, not executable retrieval. Weight decay is a training constraint that discourages weights from growing too large; the best recipe here used 0.1, but this small study does not prove that weight decay alone caused the extra pass.

Two separate contests, not one winner

The cleanest summary is one question and one result per experiment:

ExperimentQuestionMeasurementResult
General Bash imitationWhich kinds of examples should stay in the mix?Validation loss on 1,030 held-back imitation rows; lower is betterThe full mix, Arm A, finished lowest at 0.7180.
Executable file-searchWhich training recipe produces a command that actually works?One execution on each of 98 locked macOS tasks; more passes is betterThe GFR-only weight-decay checkpoint and the later Kimi hardfam recipe tied at 92/98. Keep the simpler GFR-only checkpoint.

Arm A did not “win GFR.” It won the mix-composition study. The product checkpoint is last40 wd=0.1 at 92/98. More general imitation data helps the product only if it transfers to executable file-search, which this report has not shown.

Locked file-search: 88/98 to 92/98

The progression began with Note 02’s Arm 2: two passes through the 2,402 GFR training rows at a 2e-5 learning rate, scoring 88/98. One additional epoch at the gentler 1e-5 rate reached 91/98. Adding harvest data left the score unchanged. The kept model then trained for one more GFR-only epoch with the same low rate, a cosine schedule, and weight decay 0.1, reaching 92/98.

Several plausible alternatives failed. A third epoch at the original, higher learning rate had already been discarded. Repeating the remaining hard families did not help. Starting from stock with cosine restarts fell sharply, and a recovery run remained below the best checkpoint. A final Kimi K3-suggested hard-family continue tied 92/98 but needed more repeated data, so the simpler GFR-only checkpoint stayed.

RecipeInitRowsEpochsLR / wd / schedVal lossLocked passExact gold
Note 02 Arm 2stock 1.5B2,40222e-5 / default / default0.021388/9880/98
GFR continueArm 22,40211e-5 / default / cosine0.019191/9879/98
Harvest continueGFR continue3,60311e-5 / default / cosine0.016791/9882/98
Harvest stockstock 1.5B3,60322e-5 / default / default0.018488/9880/98
Hardfam continueharvest continue3,87811e-5 / 0.01 / cosine0.015991/9883/98
Hardfam restartsstock 1.5B3,87811e-5 / 0.01 / cosine_with_restarts0.032880/9865/98
Linear from restartshardfam restarts3,87818e-6 / 0.01 / linear0.025087/9879/98
last40 wd=0.1harvest continue2,40211e-5 / 0.1 / cosine0.018392/9886/98
Kimi hardfam wd=0.1last40 wd=0.13,87811e-5 / 0.1 / cosine0.018592/9886/98

last40 wd=0.1 is the kept checkpoint. All 98 outputs remained one-line, policy-compliant, syntactically valid, and read-only. The six failures split evenly between a wrong exit status and wrong output. Because one additional pass on a 98-task set is a small difference, real-use testing matters more than the apparent one-point lead.

Locked execution pass@1 on 98 GFR components Arm 2 and harvest stock each pass 89.8 percent. GFR continue and harvest continue each pass 92.9 percent. Later last40 wd=0.1 reaches 93.9 percent and is charted below. 100% 50 0 89.8 89.8 92.9 92.9 Arm 2 harvest stock GFR 1e-5 harvest 1e-5 greedy pass@1 · 98 locked components · Darwin Bash 3.2

Low-LR continuation added three passes over Arm 2. Unexecuted harvest traces did not add a fourth.

This chart stays inside the product-training lineage. The matched external baselines are reported separately below. last40 wd=0.1 (92/98) is in the next chart.

Sample outputs to eyeball

These examples make the aggregate score concrete. The model received each shortened request once and returned one command. “Gold” is the expected command when the model’s version failed. All names come from synthetic fixtures, not private paths.

Passes (92/98)

FamilyRequestModel command
pass exact nameWalk depot for regular files named package.json. Skip lookalikes.find 'depot' -type f -name 'package.json' -print | sort
pass inameSearch lab for Makefiles, case-insensitive. Keep makefile and MAKEFILE.find 'lab' -type f -iname 'makefile' -print | sort
Gold used iname 'Makefile'. Same on macOS.
pass pruneSorted postmortem/incident markdown under ops notes. Skip site, _site, dist.find 'ops notes' \( -type d \( -name 'site' -o -name '_site' -o -name 'dist' \) -prune \) -o -type f \( -iname '*postmortem*.md' -o -iname '*incident*.md' \) -print | sort
pass newestOn macOS, return only the newest file under harbor.find 'harbor' -type f -exec stat -f '%m %N' {} + | sort -rn | head -n 1 | cut -d ' ' -f 2-
pass contentMarkdown under orchard/notes containing the literal TODO. Print matching paths.find 'orchard/notes' -type f -name '*.md' -print0 | while IFS= read -r -d '' file; do if grep -qF -- 'TODO' "$file"; then printf '%s\n' "$file"; fi; done | sort
pass rg literalTSX under console app for the literal useQuery(, line numbers, no node_modules.rg -n -F -g '*.tsx' -g '!**/node_modules/**' -- 'useQuery(' 'console app' | sort
pass jqFrom amber snapshots/list.json, print path of records where latest is true.jq -r '.snapshots[] | select(.latest == true) | .path' 'amber snapshots/list.json' | sort
pass sedShow lines 2 to 4 of atlas logs/app start.log. Leave the file untouched.sed -n '2,4p' 'atlas logs/app start.log'

Misses (6/98)

The six remaining errors are mostly small command details with large behavioral consequences: a missing exclusion, an incomplete file extension, a literal match where a word boundary was required, or a case-sensitive directory test. The model still obeyed the one-line and read-only contract on every miss; it simply returned the wrong result or exit status.

WhyRequestModelGold
exit piped rgHidden YAML under runtime. Skip .git and node_modules.rg --files 'runtime' --hidden | rg --glob '*.yaml' ...rg --files --hidden 'runtime' --glob '*.yaml' ...
exit no build pruneKotlin files under ktsrc. Print nothing if only build leftovers exist.rg --files 'ktsrc' --glob '*.kt' | sort... --glob '!**/build/**' | sort
stdout word boundaryWhole word FLUSH_SYNC under storage, hidden files on.'FLUSH_SYNC''\bFLUSH_SYNC\b'
stdout jq tagsFrom tag board/items.json, print path of records whose tags include ship.select(.tags == ["ship"])select(.tags | index("ship") != null)
exit yaml/ymllantern YAML counts for enable_cache or disable_cache.-c -F --glob '*.yaml'-c --glob '*.yaml' --glob '*.yml'
stdout case dirsFolders named test, Tests, or TEST below cedar.-name 'test' -o -name 'Tests' -o -name 'TEST'-iname 'test'

Full commands for the six misses are in samples.json. Format/policy/syntax still passed on every miss. The Kimi hardfam run also scored 92/98; it kept README-case instead of the piped-YAML miss.

Live probes, 30 August 2026

Six informal searches were also tried on the Mac with the earlier 91/98 harvest-continue checkpoint. Three were useful. The failures reveal a more practical weakness than the synthetic score: the model mishandled phrases such as “this directory” and placed find pruning logic in the wrong order. These probes are diagnostic examples, not a scored test set.

AskModel commandWhat happened
miss Historical handoff canaryfind . \( -type d \( -name 'node_modules' -o -name 'dist' -o -name 'release*' \) -prune \) -o -type f \( -iname '*handoff*' -o -iname '*hand-off*' -o -iname '*handover*' \) -print | sort -rnOne-line and policy-safe. Sorted path text, not a newest-first long listing. Still 0/1.
miss Find train_response_only.py, prune cachesfind . -type f -name 'train_response_only.py' \( -path '__pycache__' -o -path '.venv' \) -prune | sortEmpty stdout. Prune attached after -type f, so the file is dropped.
miss Find locked_eval_prompts.jsonl in this reporg --files 'this directory' --glob '*.jsonl' ...Literalized “this directory” as a folder name. Same class as the canary.
useful HTML notes under publicationfind 'publication' -maxdepth 4 -type f \( -name 'bash-sft-*.html' \) -print | sortReturned the four publication copies of notes 02 and 03.
useful SFT_LR mentions in the Ada trainer treerg -n --glob '!**/__pycache__/**' 'SFT_LR' . | sortHit the job scripts and trainer env parse. Usable.
useful bash-sft-*.html in the research folderfind . -type f -name 'bash-sft-*.html' -print | sortListed notes 01, 02, and 03.

Named fixture roots are strong. “This directory” / “current directory” and find-prune order are not. That is the real-life gap before any Ada scale-up. No GPU boxes were created for these probes; none were left running.

The harvest mix in numbers

The harvest experiment tested a tempting idea: perhaps more commands from real coding sessions would be enough, even before each command was verified. It was not. Quantity increased; locked execution accuracy did not.

Last window: 92/98, then Kimi tied it

After the 91/98 harvest continue, four more recipes ran on RTX 6000 Ada GPUs. A Kimi K3 consult then proposed one final recipe. Only the GFR-only run with weight decay 0.1 added a pass. Kimi’s follow-up tied it, so there was no reason to keep the more complicated model. Every checkpoint reported here was downloaded and hashed before its GPU was deleted.

RecipeWhat changedLockedKeep?
Hardfam continue3 extra copies of 492 executed miss-family rows (3,878 total) from harvest-continue91/98No. Same pass@1 as harvest-continue.
Hardfam restartsSame mix from stock, cosine_with_restarts80/98No. Scheduler regression.
Linear 8e-6Recover from restarts on the same mix87/98No. Below 91/98.
wd=0.1 GFR-onlyharvest-continue + GFR 2,402 + cosine wd=0.192/98Yes. +1 pass, +4 exact, no extra miss copies.
Kimi hardfam wd=0.1Continue the 92/98 checkpoint on the 3,878-row hardfam mix, same LR and wd92/98No. Tied product score, extra copies of the misses, eval loss 0.0185 vs 0.0183.
Last-window locked pass@1 Restarts 81.6, linear 88.8, hardfam continue 92.9, wd=0.1 93.9, Kimi 93.9. 100% 50 0 81.6 88.8 92.9 93.9 93.9 restarts linear 8e-6 hardfam 1e-5 wd=0.1 Kimi hardfam greedy pass@1 · 98 locked components · Darwin Bash 3.2

Weight decay 0.1 on GFR-only added the fourth pass. Copying the remaining miss families did not. The Kimi recipe tied the score and is not the keep.

93.9% is 92/98. Restarts is 80/98. Hardfam continue is still 91/98.

The hardfam mix contains the 2,402 GFR rows plus three extra copies of 492 executed examples from four weak families: find_directories, rg_count, rg_files_candidates, and rg_regex_hidden_exclude. Unlike harvest, these extra rows were verified by execution. Even so, repeating them did not push the score beyond 92/98. The result tells us repetition was not enough; it does not establish why.

Fair baselines: 90/98 versus 8/98 and 4/98

The useful comparison is not “our interface versus somebody else’s interface.” It is the same request, the same response contract, and the same scorer given to models that natively return shell commands. We therefore reran three checkpoints: the kept model, the exact stock model from which its training lineage began, and a smaller shell specialist chosen before seeing its outputs.

The specialist is AryaYT/nl2shell-0.8b, a 0.9-billion-parameter Qwen3.5 model. Its model card says it was tuned on 8,130 natural-language-to-Bash pairs plus 40 synthetic macOS examples. That makes it a plausible specialist baseline, not a claim that its training contract matches GFR.

CheckpointWhy it is herePassPolicy-safeExecutedFixture unchanged
last40 wd=0.1Kept product checkpoint, rerun with the shared comparison prompt90/9898/9898/9898/98
stock Qwen2.5-Coder 1.5BExact public starting checkpoint for the SFT lineage8/9886/9885/9898/98
NL2Shell 0.8BSmall model trained specifically for natural-language-to-shell4/9842/9842/9897/98

What the gap means. All three models returned one raw line for every request, so formatting no longer explains the result. The stock model usually produced plausible-looking commands, but exact output and exit-status semantics defeated it. The specialist often assumed absolute or home paths and used commands outside this read-only macOS contract. The sandbox blocked unsafe commands; one allowed command changed its disposable fixture and was caught as a failure. “Trained for shell” is not the same as “trained for this shell contract.”

What was held equal. The three runs used byte-identical message content: the request, the full fixture-blind evaluator contract, and one neutral formatting example. Each model used its own tokenizer and chat template, then greedy decoding with seed 42, a 1,024-token input limit, and a 256-token output limit on the same RTX 6000 Ada. Predictions were pulled and hashed before that GPU was deleted. Fixtures never went to the GPU; strict execution happened afterward on macOS Bash 3.2.

Why 90 here but 92 above? The frozen primary result used the product prompt and remains 92/98. This comparison adds a longer contract explanation so unfamiliar public models are not penalized for hidden rules; under that matched prompt, the kept checkpoint passes 90/98. Do not average the two numbers: 92 describes the selected product run, while 90 is the like-for-like baseline run. Against stock, the matched gain is 83.7 percentage points (95% paired component-bootstrap interval: +75.5 to +90.8 points). The specialist is 4.1 points below stock, with an interval from −10.2 to +2.0 points.

Possible next steps

The next stage should reduce uncertainty about real use, not add another nearby training score.

  1. Real-life gate. Run the last40 wd=0.1 checkpoint on live Mini file-search, including the six remaining locked families and the historical handoff canary. Do not train on the canary.
  2. Execute, then scale. Promote the 218 quarantined harvest candidates only after macOS execution. Do not upsample the current six misses again.
  3. Then many Adas. New LRs and schedules on that larger execution-backed mix. Do not rerun packed SFT, LoRA, 3B, repair-only 5e-6, extra 2e-5 epochs, cosine_with_restarts, or another hardfam copy of the same misses.
  4. Keep GFR 98 as a regression gate. Select on real-use tasks frozen before the next data drop. Never publish training JSONL.

Next success criterion: the 92/98 model is useful on a live search the operator actually needed, without dropping contract rates, before we pay for a GPU farm.

Evidence and reproducibility

The public evidence packet contains aggregate metrics, selected sanitized examples, and hashes. It excludes training JSONL, private traces, prompts with personal paths, full completion dumps, fixtures, credentials, and weight tensors.

Kept checkpoint: last40 wd=0.1, Qwen2.5-Coder-1.5B-Instruct full SFT, GFR 2,402 rows × 1 epoch, LR 1e-5, cosine, weight decay 0.1, max length 1,024. Unpublished model.safetensors SHA-256 b3558c0f140a659f0c661bf9795ed59049cdfed5db42c1e3e8e8480e8ad901b2. Train wall 57.3 s; peak 15.22 GiB on one NVIDIA RTX 6000 Ada Generation GPU. Kimi hardfam weights (not kept) SHA-256 e57515db06eb34a0a70ca33ddf2c9a23255206b492477e25c8c4ecad3cc56d41.

Bottom line

For general Bash imitation, keep the mixture of solutions, repairs, and trajectories. For executable file-search, the simplest winning recipe was one more low-learning-rate GFR epoch with weight decay 0.1. More unverified data and more copies of known hard cases did not help. Do not scale GPUs until the 92/98 checkpoint succeeds on real searches and the next training rows have been execution-checked.