BugInfrastructure

verify:lifecycle-visible Falsely Reports Lifecycle Paths as Git-Ignored Due to Blank .gitignore Lines Matching Everything

The verify:lifecycle-visible check in Directive Engine 0.107.0 incorrectly passes blank (whitespace-only) lines from .gitignore into its pattern matcher.

Rootlock SRE Engine 6 min read
Diagnostic brief

At a Glance

The verify:lifecycle-visible check in Directive Engine 0.107.0 incorrectly passes blank (whitespace-only) lines from .gitignore into its pattern matcher.

Severity Not rated
Confidence Medium
Frequency Unknown
Impact See analysis

Summary

The verify:lifecycle-visible check in Directive Engine 0.107.0 incorrectly passes blank (whitespace-only) lines from .gitignore into its pattern matcher. Because an empty string matches every path, the check falsely concludes that all lifecycle root directories are git-ignored, even when they are fully tracked. Any .gitignore file with blank separator lines — which is nearly universal — triggers a false advisory on every deft session:start.

Root-Cause Analysis

Confirmed evidence:

  • Line 51 of the affected .gitignore is blank.
  • Every reported hidden path is attributed to (.gitignore:51:) — the pattern field after the colon is empty, not a real glob.
  • All five lifecycle roots (xbrief/proposed/, xbrief/pending/, xbrief/active/, xbrief/completed/, xbrief/cancelled/) share the same attribution line, which is the signature of a single match-all pattern, not individual per-path rules.
  • git check-ignore -v <path> returns nothing for the affected paths, and git ls-files enumerates the lifecycle artifacts — confirming that git itself does not consider them ignored.

Root cause (confirmed by evidence):

The .gitignore parser inside verify:lifecycle-visible iterates lines without filtering out blank entries before constructing or evaluating patterns. When a blank line is handed to the pattern engine, the resulting empty pattern matches every input path. This is a violation of the git .gitignore specification, which states explicitly:

> A blank line matches no files, so it can serve as a separator for readability.

The attribution string format (.gitignore:<line>:<pattern>) exposes the bug directly: an empty pattern field after the line number means the parser consumed the blank line as a valid rule.

Reasonable inference:

Comment lines (lines beginning with #) may be subject to the same omission if only blank-line filtering was missed; however, the issue report does not confirm a comment-line failure independently.

Alternative causes considered and ruled out:

  • Consumer .gitignore misconfiguration — ruled out; git check-ignore and git ls-files both confirm the paths are tracked.
  • A negation or wildcard rule elsewhere in .gitignore — ruled out; all attributions point to the single blank line 51, not to any glob pattern.
  • Platform-specific line-ending parsing (\r\n producing a non-empty trimmed line) — possible contributing factor on Windows but not confirmed; treat as secondary.

Resolution Steps

Immediate workaround (consumer-side)

  1. Identify the blank line(s) triggering the false match by examining the attribution in the advisory output. The format (.gitignore:<line>:) with an empty pattern after the final colon pinpoints the offending line numbers.
  2. Confirm independently that your lifecycle paths are genuinely tracked:
git check-ignore -v xbrief/proposed/ xbrief/pending/ xbrief/active/ xbrief/completed/ xbrief/cancelled/
git ls-files xbrief/

If both commands confirm the paths are tracked (no output from check-ignore, paths listed by ls-files), the advisory is a false positive and can be safely disregarded until the upstream fix ships.

  1. Do not remove blank lines from your .gitignore as a workaround — blank lines are valid and semantically meaningful as separators; removing them would mask the bug rather than fix it and degrade .gitignore readability for no correct reason.
  2. Do not run task verify:lifecycle-visible -- --enforce in CI pipelines until the fix is released. The --enforce flag converts this false advisory into a hard stop.

Upstream fix (framework maintainers)

  1. In the .gitignore line iterator within the verify:lifecycle-visible implementation, add a pre-filter step before pattern construction:
  • Skip lines that are empty or contain only whitespace characters after stripping.
  • Skip lines whose first non-whitespace character is # (comment lines per git spec).
  • Treat any pattern that evaluates to an empty string after parsing as a hard parse error (panic/throw) rather than silently constructing a match-all predicate.
  1. Add a regression fixture: a synthetic .gitignore containing blank lines, # comment lines, a negation pattern (!keep-this/), a standard glob, and a trailing newline. Assert that none of the lifecycle roots are reported as hidden when they are absent from any real pattern in that file.

CLI Commands

Verify that the paths are genuinely tracked by git (expected output: no lines from check-ignore, all lifecycle files listed by ls-files):

# Should produce NO output if paths are tracked
git check-ignore -v \
  xbrief/proposed/ \
  xbrief/pending/ \
  xbrief/active/ \
  xbrief/completed/ \
  xbrief/cancelled/

# Should list lifecycle files
git ls-files xbrief/

Identify which .gitignore line is blank and triggering the match:

# Print line numbers alongside content; blank lines will show only the number
grep -n "" .gitignore | grep -E "^[0-9]+:@@ROOTLOCK_CODE_2@@quot;

Inspect line 51 (substitute the actual line number from the advisory):

sed -n '51p' .gitignore | cat -A

The cat -A flag will render a truly blank line as an empty output and expose any hidden carriage returns (^M$) that could cause a non-empty trimmed result on some platforms.

Run the lifecycle-visible check in isolation to confirm the false positive:

task verify:lifecycle-visible

Verification

After the upstream parser fix is released and you have upgraded to the patched version:

  1. Run deft session:start in a repo whose .gitignore contains blank separator lines.
  2. The advisory block should be absent from the output entirely if all lifecycle roots are tracked.
  3. Run the check explicitly:
task verify:lifecycle-visible

Expected healthy output: no [deft lifecycle-visible] hidden lines, and no ADVISORY block.

  1. Confirm the attribution format for any legitimately ignored path now shows a non-empty pattern after the final colon — e.g., (.gitignore:12:build/) — never (.gitignore:<line>:) with a trailing empty field.

Rollback indicator: If the advisory reappears after upgrading, re-run git check-ignore -v on the affected paths. If git check-ignore still returns nothing, the bug has regressed and should be re-filed against the new version.

Prevention

For framework maintainers:

  • Encode the three-rule git blank-line/comment/negation spec as a dedicated unit-tested parser function, separate from glob compilation, so the filtering contract is explicit and independently verifiable.
  • Add a property-based or fuzz test that feeds arbitrary .gitignore content and asserts that no path absent from real glob patterns is ever returned as hidden.
  • Gate releases of verify:lifecycle-visible on the regression fixture described in Resolution Step 6.
  • Lint the pattern pipeline to assert that an empty-string pattern is unreachable after the filter step (a static assertion or a runtime invariant check at pattern construction time).

For platform/DevOps engineers consuming the framework:

  • Until the fix ships, exclude task verify:lifecycle-visible -- --enforce from required CI gates. Keep it advisory-only (warn-only default) to prevent build-breaking false positives.
  • Add a canary check in your CI pipeline that cross-references the advisory output against git check-ignore for any path the tool reports as hidden. If git check-ignore disagrees, flag the discrepancy rather than acting on the advisory:
#!/usr/bin/env bash
# Canary: cross-validate lifecycle-visible advisory against git
PATHS=("xbrief/proposed/" "xbrief/pending/" "xbrief/active/" "xbrief/completed/" "xbrief/cancelled/")
for p in "${PATHS[@]}"; do
  if git check-ignore -q "$p"; then
    echo "CONFIRMED IGNORED: $p"
  else
    echo "FALSE POSITIVE SUSPECTED: $p is tracked by git"
  fi
done
  • Monitor the upstream issue tracker for the patch release and include the version bump in your next dependency update cycle with an explicit changelog reference.
Developer FirstBuilt for engineers solving real problems
Evidence DrivenTechnical claims tied to available evidence
Automation ReadyStructured for CLI, APIs, and workflows
Privacy FocusedNo unnecessary data collection in this article UI
STAY AHEAD OF ISSUES

Get new root-cause analyses in your inbox

Engineering-focused updates. No fake subscriber counts. Unsubscribe anytime.