From 3b09be817fea27c7550cedb554f9ed31d34c99f1 Mon Sep 17 00:00:00 2001 From: NagyVikt Date: Thu, 23 Apr 2026 12:16:36 +0200 Subject: [PATCH] Stage parent gitlinks without status heuristics Registered submodules can ignore pointer changes strongly enough that git add leaves the parent index unchanged. The finish flow now stages the exact nested HEAD with git update-index --cacheinfo after validating the parent path is already a 160000 gitlink. Constraint: Parent repositories can hide submodule diffs through both diff.ignoreSubmodules and submodule..ignore. Rejected: Keep using git add after SHA detection | live dogfood showed git add can still leave the gitlink index unchanged. Confidence: high Scope-risk: narrow Directive: Parent gitlink staging is intentionally cacheinfo-based and limited to existing 160000 entries only. Tested: bash -n scripts/agent-branch-finish.sh; bash -n templates/scripts/agent-branch-finish.sh; node --test test/finish.test.js; openspec validate agent-codex-auto-commit-parent-subrepo-upgrades-2026-04-23-11-59 --strict; git diff --check Not-tested: Full npm test after cacheinfo follow-up; full suite passed before the dogfood fixes --- .../specs/finish/spec.md | 2 +- .../tasks.md | 1 + scripts/agent-branch-finish.sh | 23 +++++++++++-------- templates/scripts/agent-branch-finish.sh | 23 +++++++++++-------- test/finish.test.js | 2 ++ 5 files changed, 30 insertions(+), 21 deletions(-) diff --git a/openspec/changes/agent-codex-auto-commit-parent-subrepo-upgrades-2026-04-23-11-59/specs/finish/spec.md b/openspec/changes/agent-codex-auto-commit-parent-subrepo-upgrades-2026-04-23-11-59/specs/finish/spec.md index 42eda2d..b14be20 100644 --- a/openspec/changes/agent-codex-auto-commit-parent-subrepo-upgrades-2026-04-23-11-59/specs/finish/spec.md +++ b/openspec/changes/agent-codex-auto-commit-parent-subrepo-upgrades-2026-04-23-11-59/specs/finish/spec.md @@ -10,7 +10,7 @@ After a successful nested repository finish, Guardex SHALL detect when the finis - **WHEN** `gx branch finish` merges the nested agent branch and fast-forwards the nested base worktree - **THEN** Guardex commits the parent repo gitlink path with a message naming that subrepo pointer - **AND** unrelated parent staged paths are not included in that commit -- **AND** parent `diff.ignoreSubmodules=all` settings do not hide the gitlink pointer update from this detection +- **AND** parent submodule ignore settings do not hide or prevent staging the gitlink pointer update #### Scenario: Parent auto-commit is disabled diff --git a/openspec/changes/agent-codex-auto-commit-parent-subrepo-upgrades-2026-04-23-11-59/tasks.md b/openspec/changes/agent-codex-auto-commit-parent-subrepo-upgrades-2026-04-23-11-59/tasks.md index 86c7c84..822fca0 100644 --- a/openspec/changes/agent-codex-auto-commit-parent-subrepo-upgrades-2026-04-23-11-59/tasks.md +++ b/openspec/changes/agent-codex-auto-commit-parent-subrepo-upgrades-2026-04-23-11-59/tasks.md @@ -13,6 +13,7 @@ - [x] Add parent gitlink detection and path-scoped parent commit to `agent-branch-finish`. - [x] Pass parent gitlink controls through `gx finish`. - [x] Compare gitlink index SHA to nested HEAD so parent `diff.ignoreSubmodules=all` does not hide pointer upgrades. +- [x] Stage the gitlink with `git update-index --cacheinfo` so registered submodule ignore settings cannot suppress the pointer update. ## 4. Cleanup diff --git a/scripts/agent-branch-finish.sh b/scripts/agent-branch-finish.sh index 58768bf..48e3ff6 100755 --- a/scripts/agent-branch-finish.sh +++ b/scripts/agent-branch-finish.sh @@ -519,7 +519,7 @@ maybe_auto_commit_parent_gitlink() { local gitlink_index_sha="" local gitlink_parent_head_sha="" local subrepo_head_sha="" - local add_output="" + local update_index_output="" local commit_output="" local commit_message="" @@ -563,21 +563,24 @@ maybe_auto_commit_parent_gitlink() { gitlink_index_sha="$(git -C "$super_root" ls-files -s -- "$subrepo_rel" | awk 'NR == 1 { print $2 }')" gitlink_parent_head_sha="$(git -C "$super_root" ls-tree HEAD -- "$subrepo_rel" | awk 'NR == 1 { print $3 }')" subrepo_head_sha="$(git -C "$repo_common_root" rev-parse HEAD 2>/dev/null || true)" - if [[ -n "$gitlink_index_sha" && "$gitlink_index_sha" == "$gitlink_parent_head_sha" && "$gitlink_index_sha" == "$subrepo_head_sha" ]]; then + if [[ -z "$subrepo_head_sha" ]]; then return 0 fi - - if ! add_output="$(git -C "$super_root" add -- "$subrepo_rel" 2>&1)"; then - echo "[agent-branch-finish] Warning: parent gitlink staging failed for ${subrepo_rel} in ${super_root}." >&2 - [[ -n "$add_output" ]] && echo "$add_output" >&2 + if [[ -n "$gitlink_index_sha" && "$gitlink_index_sha" == "$gitlink_parent_head_sha" && "$gitlink_index_sha" == "$subrepo_head_sha" ]]; then return 0 fi - if git -C "$super_root" diff --cached --quiet -- "$subrepo_rel"; then - gitlink_index_sha="$(git -C "$super_root" ls-files -s -- "$subrepo_rel" | awk 'NR == 1 { print $2 }')" - gitlink_parent_head_sha="$(git -C "$super_root" ls-tree HEAD -- "$subrepo_rel" | awk 'NR == 1 { print $3 }')" - if [[ "$gitlink_index_sha" == "$gitlink_parent_head_sha" ]]; then + + if [[ "$gitlink_index_sha" != "$subrepo_head_sha" ]]; then + if ! update_index_output="$(git -C "$super_root" update-index --cacheinfo 160000 "$subrepo_head_sha" "$subrepo_rel" 2>&1)"; then + echo "[agent-branch-finish] Warning: parent gitlink staging failed for ${subrepo_rel} in ${super_root}." >&2 + [[ -n "$update_index_output" ]] && echo "$update_index_output" >&2 return 0 fi + gitlink_index_sha="$(git -C "$super_root" ls-files -s -- "$subrepo_rel" | awk 'NR == 1 { print $2 }')" + fi + gitlink_parent_head_sha="$(git -C "$super_root" ls-tree HEAD -- "$subrepo_rel" | awk 'NR == 1 { print $3 }')" + if [[ "$gitlink_index_sha" == "$gitlink_parent_head_sha" ]]; then + return 0 fi commit_message="Update ${subrepo_rel} subrepo pointer" diff --git a/templates/scripts/agent-branch-finish.sh b/templates/scripts/agent-branch-finish.sh index 58768bf..48e3ff6 100755 --- a/templates/scripts/agent-branch-finish.sh +++ b/templates/scripts/agent-branch-finish.sh @@ -519,7 +519,7 @@ maybe_auto_commit_parent_gitlink() { local gitlink_index_sha="" local gitlink_parent_head_sha="" local subrepo_head_sha="" - local add_output="" + local update_index_output="" local commit_output="" local commit_message="" @@ -563,21 +563,24 @@ maybe_auto_commit_parent_gitlink() { gitlink_index_sha="$(git -C "$super_root" ls-files -s -- "$subrepo_rel" | awk 'NR == 1 { print $2 }')" gitlink_parent_head_sha="$(git -C "$super_root" ls-tree HEAD -- "$subrepo_rel" | awk 'NR == 1 { print $3 }')" subrepo_head_sha="$(git -C "$repo_common_root" rev-parse HEAD 2>/dev/null || true)" - if [[ -n "$gitlink_index_sha" && "$gitlink_index_sha" == "$gitlink_parent_head_sha" && "$gitlink_index_sha" == "$subrepo_head_sha" ]]; then + if [[ -z "$subrepo_head_sha" ]]; then return 0 fi - - if ! add_output="$(git -C "$super_root" add -- "$subrepo_rel" 2>&1)"; then - echo "[agent-branch-finish] Warning: parent gitlink staging failed for ${subrepo_rel} in ${super_root}." >&2 - [[ -n "$add_output" ]] && echo "$add_output" >&2 + if [[ -n "$gitlink_index_sha" && "$gitlink_index_sha" == "$gitlink_parent_head_sha" && "$gitlink_index_sha" == "$subrepo_head_sha" ]]; then return 0 fi - if git -C "$super_root" diff --cached --quiet -- "$subrepo_rel"; then - gitlink_index_sha="$(git -C "$super_root" ls-files -s -- "$subrepo_rel" | awk 'NR == 1 { print $2 }')" - gitlink_parent_head_sha="$(git -C "$super_root" ls-tree HEAD -- "$subrepo_rel" | awk 'NR == 1 { print $3 }')" - if [[ "$gitlink_index_sha" == "$gitlink_parent_head_sha" ]]; then + + if [[ "$gitlink_index_sha" != "$subrepo_head_sha" ]]; then + if ! update_index_output="$(git -C "$super_root" update-index --cacheinfo 160000 "$subrepo_head_sha" "$subrepo_rel" 2>&1)"; then + echo "[agent-branch-finish] Warning: parent gitlink staging failed for ${subrepo_rel} in ${super_root}." >&2 + [[ -n "$update_index_output" ]] && echo "$update_index_output" >&2 return 0 fi + gitlink_index_sha="$(git -C "$super_root" ls-files -s -- "$subrepo_rel" | awk 'NR == 1 { print $2 }')" + fi + gitlink_parent_head_sha="$(git -C "$super_root" ls-tree HEAD -- "$subrepo_rel" | awk 'NR == 1 { print $3 }')" + if [[ "$gitlink_index_sha" == "$gitlink_parent_head_sha" ]]; then + return 0 fi commit_message="Update ${subrepo_rel} subrepo pointer" diff --git a/test/finish.test.js b/test/finish.test.js index 7263e64..37a600c 100644 --- a/test/finish.test.js +++ b/test/finish.test.js @@ -190,6 +190,8 @@ test('agent-branch-finish auto-commits parent gitlink after nested repo finish', assert.equal(result.status, 0, result.stderr || result.stdout); result = runCmd('git', ['config', 'diff.ignoreSubmodules', 'all'], parentDir); assert.equal(result.status, 0, result.stderr || result.stdout); + result = runCmd('git', ['config', 'submodule.packages/child.ignore', 'all'], parentDir); + assert.equal(result.status, 0, result.stderr || result.stdout); fs.writeFileSync(path.join(parentDir, 'unrelated-parent.txt'), 'already staged\n', 'utf8'); result = runCmd('git', ['add', 'unrelated-parent.txt'], parentDir); assert.equal(result.status, 0, result.stderr || result.stdout);