Skip to content

Dockerfile grammar flags stock .NET Dockerfiles as parse_partial #1610

Closed

Description

@vitaliy-shatskiy

Version

codebase-memory-mcp 0.10.2

(also present in v0.10.3 — the release notes mention no Dockerfile grammar change)

Platform

Windows (x64)

Install channel

GitHub release archive / install.sh / install.ps1

Binary variant

standard

What happened, and what did you expect?

Unmodified Visual Studio-generated Dockerfiles are flagged parse_partial on standard lines — the exec-form ENTRYPOINT JSON array and COPY --from=<stage>. Both are stock syntax emitted by VS "Add Docker Support" for every ASP.NET project, so the flag is effectively permanent for .NET repositories: measured on a 22-repo .NET fleet, this put a persistent <name>::missed shadow row into most stores.

On its own this is only noise in check_index_coverage, but it becomes load-bearing through #1609 (any shadow row disqualifies the project from cross-repo linking), which is why I'm reporting the grammar gap rather than shrugging it off.

I expected stock Dockerfile syntax to parse cleanly — or, if partial Dockerfile coverage is by design, for Dockerfile-grammar misses not to create shadow rows.

Reproduction

  1. Code being indexed — a dummy repo whose root Dockerfile contains:

    FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
    COPY --from=publish /app/publish .
    ENTRYPOINT ["dotnet", "MyApp.dll"]
  2. Command:

    codebase-memory-mcp cli index_repository --repo-path /tmp/demo --mode full
    codebase-memory-mcp cli check_index_coverage '{"project":"demo","paths":["Dockerfile"]}'
    
  3. Result: the Dockerfile is reported parse_partial, and the store's projects table gains demo::missed.
    Expected: clean parse (exec-form ENTRYPOINT/CMD and COPY --from=<stage> are standard Dockerfile syntax).

Logs

(no error output — the flag only surfaces in check_index_coverage and as the ::missed shadow row)

Confirmations

  • I searched existing issues and this is not a duplicate.
  • My reproduction uses shareable code (a dummy snippet or a public OSS repository), not proprietary code.

Activity

  1. vitaliy-shatskiy commented on Aug 14, 2026

    @vitaliy-shatskiy
    Author

    The cross-repo validation bug this feeds into is #1609.

  2. DeusData commented on Aug 15, 2026

    @DeusData
    Owner

    Thank you for this, and for the other two reports filed alongside it — #1609 is fixed on main and ships in v0.10.5, and #1611 reproduced exactly as you described.

    This one I could not reproduce, and I want to be transparent about what I tried rather than just asking you to try again:

    • your 3-line Dockerfile, exactly as filed → parse_partial_count: 0, check_index_coverage returns no_recorded_issue
    • the full stock Visual Studio multi-stage Dockerfile (21 lines: ARG, USER $APP_UID, COPY --from=publish, exec-form ENTRYPOINT) → 0
    • a CRLF-line-ending variant of the same → 0

    I also checked that this is a fair test of your version: internal/cbm/vendored/grammars/dockerfile and pass_infrascan.c have had no commits since v0.10.1, so the binary I tested is a valid proxy for the 0.10.2/0.10.3 behaviour you saw.

    So something in the real file differs from the reduction, and it is more likely that my reproduction is missing it than that your report is wrong — your other two reports verified precisely, including one that turned out to be a genuine blocker nobody else had found.

    Two things would settle it, whichever is easier:

    1. The actual Dockerfile that triggers it (redacted as you like — the structure is what matters: line endings, a BOM, unusual RUN continuations, heredocs, non-ASCII bytes in a LABEL).
    2. Or just the check_index_coverage output for that path — the error_ranges field gives the exact line span the parser choked on, which usually identifies the construct on its own.

    No rush, and thank you for the care in all three reports.

  3. vitaliy-shatskiy commented on Aug 15, 2026

    @vitaliy-shatskiy
    Author

    Found it, and your reproduction was missing one byte: the file has no final newline. That is the whole trigger — not the BOM, not CRLF, not the instruction form.

    I can't share the original file, so I isolated the property instead and rebuilt it from scratch. Every fixture below is synthetic, written byte-exactly from a script (an editor would silently add the newline back and hide the bug).

    Minimal reproduction — two lines, LF, no trailing newline:

    # write_dockerfile.py
    data = b'FROM mcr.microsoft.com/dotnet/aspnet:8.0\nENTRYPOINT ["dotnet", "App.dll"]'  # note: no trailing \n
    open("Dockerfile", "wb").write(data)
    codebase-memory-mcp cli index_repository --repo-path <dir> --mode full
    -> parse_partial_count: 1
    

    Append a single \n to the same bytes and it is 0.

    Isolation matrix (each variant indexed as its own throwaway project, v0.10.4, Windows x64, release archive):

    BOM line endings final newline last instruction parse_partial_count
    no LF yes ENTRYPOINT [...] 0
    no CRLF yes ENTRYPOINT [...] 0
    yes CRLF yes ENTRYPOINT [...] 0
    yes LF yes ENTRYPOINT [...] 0
    no CRLF no ENTRYPOINT [...] 1
    yes CRLF no ENTRYPOINT [...] 1
    no LF no ENTRYPOINT [...] 1
    no CRLF no WORKDIR /app 1
    no CRLF no ENTRYPOINT dotnet App.dll (shell form) 1
    no CRLF no 2-line file, FROM + ENTRYPOINT [...] 1

    So it is independent of the BOM, of the line-ending style, of exec-form vs shell-form, and of file length. The flagged range is always the last line, and check_index_coverage reports it as a one-line span ("kind":"parse_partial", ranges:[{start:N,end:N}] where N is that last line).

    For the original file that started this report, the structural facts are: UTF-8 BOM, CRLF throughout, 25 lines, no trailing newline, no tabs, no line continuations, no heredocs, and the only non-ASCII bytes in the file are the three BOM bytes. Coverage flagged 25-25 — the unterminated line.

    That also explains the shape of the false signal: it is not "this construct confused the parser", it is "the file did not end the way the grammar expects", so any Dockerfile saved without a trailing newline reports a phantom partial parse — and, until your #1609 fix, dragged that project out of cross-repo linking with it.

  4. DeusData commented on Aug 15, 2026

    @DeusData
    Owner

    Confirmed — reproduced here on the first try with your byte-exact fixture:

    NO trailing newline   -> parse_partial_count: 1
    WITH trailing newline -> parse_partial_count: 0
    

    Same two lines, same bytes, one \n apart. You were right and my reproduction was wrong: I built the fixtures with an editor and a heredoc, both of which append the final newline silently, so every variant I tried was the terminated case. Writing them from a script, exactly as you did, is the only way to see it — that detail is the most valuable part of your report.

    Your isolation matrix also settles the shape of it: independent of BOM, of CRLF vs LF, of exec-form vs shell-form, and of file length, with the flagged range always the final line. That is not "a construct confused the parser", it is "the file did not end the way the grammar expects" — so any Dockerfile saved without a trailing newline reports a phantom partial parse. Plenty of tools write files that way, so this is likely under-reported rather than rare.

    And your closing observation is the one that makes this more than cosmetic:

    until your #1609 fix, dragged that project out of cross-repo linking with it

    That is exactly right, and it means these two issues compounded. A phantom parse_partial created a <name>::missed shadow row, and that shadow row made the project fail cross-repo validation as both source and target. So a single missing newline could silently remove an entire repository from cross-repo intelligence, with no error anywhere. #1609 is fixed on main and ships in v0.10.5, which breaks that chain — but the phantom flag itself is still wrong and still worth fixing.

    Not in v0.10.5 — that release is being cut right now and I am not putting an unbisected grammar/scan change into it at the last moment. It goes straight into the next one. Now that there is a deterministic two-line reproduction the fix is tractable: the question is whether the final unterminated line should be fed to the parser with a synthetic terminator, or whether the partial-parse signal should not fire for a range that is merely unterminated at EOF. I would rather answer that with the reproduction in front of me than guess.

    Thank you for going back and isolating this properly instead of just resending the file. Three reports in one night, all three verified, one of them a genuine blocker nobody else had found.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingwindowsWindows-specific issues

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions