Skip to content

Commit f5cb149

Browse files
ggallenclaude
andcommitted
fix: load per-repo config.yaml in fullsend run and reusable workflows
Per-repo installations store config.yaml in .fullsend/ but the CLI only parsed OrgConfig format. Add OrgConfigFromPerRepo adapter and teach tryLoadFullsendConfig/requireFullsendConfig to fall back to PerRepoConfig parsing using structural discrimination (isPerRepoYAML). Update all six reusable workflows to layer workspace files under .fullsend/ when install_mode is per-repo, and pass fullsend-dir to the action invocation. Closes #2970 Signed-off-by: Greg Allen <greg@fullsend.ai> Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Greg Allen <gallen@redhat.com>
1 parent 6cfeae9 commit f5cb149

13 files changed

Lines changed: 378 additions & 69 deletions

.github/workflows/reusable-code.yml

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -88,15 +88,20 @@ jobs:
8888
run: |
8989
set -euo pipefail
9090
if [[ "${INSTALL_MODE}" != "per-org" && "${INSTALL_MODE}" != "per-repo" ]]; then
91-
echo "::error::Invalid install_mode '${INSTALL_MODE}': must be 'per-org' or 'per-repo'"
91+
printf 'Received install_mode: %q\n' "${INSTALL_MODE}"
92+
echo "::error::Invalid install_mode: must be 'per-org' or 'per-repo'"
9293
exit 1
9394
fi
9495
SRC=".defaults/internal/scaffold/fullsend-repo"
9596
LAYERED_DIRS="agents skills schemas harness plugins policies scripts env"
97+
DEST=""
98+
if [[ "${INSTALL_MODE}" == "per-repo" ]]; then
99+
DEST=".fullsend/"
100+
fi
96101
for dir in ${LAYERED_DIRS}; do
97102
if [[ -d "${SRC}/${dir}" ]]; then
98-
mkdir -p "${dir}"
99-
cp -r "${SRC}/${dir}/." "${dir}/"
103+
mkdir -p "${DEST}${dir}"
104+
cp -r "${SRC}/${dir}/." "${DEST}${dir}/"
100105
fi
101106
done
102107
CUSTOM_BASE="customized"
@@ -108,8 +113,8 @@ jobs:
108113
find "${CUSTOM_BASE}/${dir}" -type f ! -name '.gitkeep' -print0 \
109114
| while IFS= read -r -d '' f; do
110115
rel="${f#"${CUSTOM_BASE}"/}"
111-
mkdir -p "$(dirname "${rel}")"
112-
cp "${f}" "${rel}"
116+
mkdir -p "$(dirname "${DEST}${rel}")"
117+
cp "${f}" "${DEST}${rel}"
113118
done
114119
fi
115120
done
@@ -195,6 +200,7 @@ jobs:
195200
with:
196201
agent: code
197202
version: ${{ inputs.fullsend_version }}
203+
fullsend-dir: ${{ inputs.install_mode == 'per-repo' && '.fullsend' || '' }}
198204
run-url: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
199205
status-repo: ${{ inputs.source_repo }}
200206
status-number: ${{ fromJSON(inputs.event_payload).issue.number }}

.github/workflows/reusable-fix.yml

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,15 +104,20 @@ jobs:
104104
run: |
105105
set -euo pipefail
106106
if [[ "${INSTALL_MODE}" != "per-org" && "${INSTALL_MODE}" != "per-repo" ]]; then
107-
echo "::error::Invalid install_mode '${INSTALL_MODE}': must be 'per-org' or 'per-repo'"
107+
printf 'Received install_mode: %q\n' "${INSTALL_MODE}"
108+
echo "::error::Invalid install_mode: must be 'per-org' or 'per-repo'"
108109
exit 1
109110
fi
110111
SRC=".defaults/internal/scaffold/fullsend-repo"
111112
LAYERED_DIRS="agents skills schemas harness plugins policies scripts env"
113+
DEST=""
114+
if [[ "${INSTALL_MODE}" == "per-repo" ]]; then
115+
DEST=".fullsend/"
116+
fi
112117
for dir in ${LAYERED_DIRS}; do
113118
if [[ -d "${SRC}/${dir}" ]]; then
114-
mkdir -p "${dir}"
115-
cp -r "${SRC}/${dir}/." "${dir}/"
119+
mkdir -p "${DEST}${dir}"
120+
cp -r "${SRC}/${dir}/." "${DEST}${dir}/"
116121
fi
117122
done
118123
CUSTOM_BASE="customized"
@@ -124,8 +129,8 @@ jobs:
124129
find "${CUSTOM_BASE}/${dir}" -type f ! -name '.gitkeep' -print0 \
125130
| while IFS= read -r -d '' f; do
126131
rel="${f#"${CUSTOM_BASE}"/}"
127-
mkdir -p "$(dirname "${rel}")"
128-
cp "${f}" "${rel}"
132+
mkdir -p "$(dirname "${DEST}${rel}")"
133+
cp "${f}" "${DEST}${rel}"
129134
done
130135
fi
131136
done
@@ -396,6 +401,7 @@ jobs:
396401
with:
397402
agent: fix
398403
version: ${{ inputs.fullsend_version }}
404+
fullsend-dir: ${{ inputs.install_mode == 'per-repo' && '.fullsend' || '' }}
399405
run-url: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
400406
status-repo: ${{ inputs.source_repo }}
401407
status-number: ${{ steps.context.outputs.pr_number }}

.github/workflows/reusable-prioritize.yml

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,15 +89,20 @@ jobs:
8989
run: |
9090
set -euo pipefail
9191
if [[ "${INSTALL_MODE}" != "per-org" && "${INSTALL_MODE}" != "per-repo" ]]; then
92-
echo "::error::Invalid install_mode '${INSTALL_MODE}': must be 'per-org' or 'per-repo'"
92+
printf 'Received install_mode: %q\n' "${INSTALL_MODE}"
93+
echo "::error::Invalid install_mode: must be 'per-org' or 'per-repo'"
9394
exit 1
9495
fi
9596
SRC=".defaults/internal/scaffold/fullsend-repo"
9697
LAYERED_DIRS="agents skills schemas harness plugins policies scripts env"
98+
DEST=""
99+
if [[ "${INSTALL_MODE}" == "per-repo" ]]; then
100+
DEST=".fullsend/"
101+
fi
97102
for dir in ${LAYERED_DIRS}; do
98103
if [[ -d "${SRC}/${dir}" ]]; then
99-
mkdir -p "${dir}"
100-
cp -r "${SRC}/${dir}/." "${dir}/"
104+
mkdir -p "${DEST}${dir}"
105+
cp -r "${SRC}/${dir}/." "${DEST}${dir}/"
101106
fi
102107
done
103108
CUSTOM_BASE="customized"
@@ -109,8 +114,8 @@ jobs:
109114
find "${CUSTOM_BASE}/${dir}" -type f ! -name '.gitkeep' -print0 \
110115
| while IFS= read -r -d '' f; do
111116
rel="${f#"${CUSTOM_BASE}"/}"
112-
mkdir -p "$(dirname "${rel}")"
113-
cp "${f}" "${rel}"
117+
mkdir -p "$(dirname "${DEST}${rel}")"
118+
cp "${f}" "${DEST}${rel}"
114119
done
115120
fi
116121
done
@@ -151,4 +156,5 @@ jobs:
151156
with:
152157
agent: prioritize
153158
version: ${{ inputs.fullsend_version }}
159+
fullsend-dir: ${{ inputs.install_mode == 'per-repo' && '.fullsend' || '' }}
154160
mint-url: ${{ inputs.mint_url }}

.github/workflows/reusable-retro.yml

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,15 +85,20 @@ jobs:
8585
run: |
8686
set -euo pipefail
8787
if [[ "${INSTALL_MODE}" != "per-org" && "${INSTALL_MODE}" != "per-repo" ]]; then
88-
echo "::error::Invalid install_mode '${INSTALL_MODE}': must be 'per-org' or 'per-repo'"
88+
printf 'Received install_mode: %q\n' "${INSTALL_MODE}"
89+
echo "::error::Invalid install_mode: must be 'per-org' or 'per-repo'"
8990
exit 1
9091
fi
9192
SRC=".defaults/internal/scaffold/fullsend-repo"
9293
LAYERED_DIRS="agents skills schemas harness plugins policies scripts env"
94+
DEST=""
95+
if [[ "${INSTALL_MODE}" == "per-repo" ]]; then
96+
DEST=".fullsend/"
97+
fi
9398
for dir in ${LAYERED_DIRS}; do
9499
if [[ -d "${SRC}/${dir}" ]]; then
95-
mkdir -p "${dir}"
96-
cp -r "${SRC}/${dir}/." "${dir}/"
100+
mkdir -p "${DEST}${dir}"
101+
cp -r "${SRC}/${dir}/." "${DEST}${dir}/"
97102
fi
98103
done
99104
CUSTOM_BASE="customized"
@@ -105,8 +110,8 @@ jobs:
105110
find "${CUSTOM_BASE}/${dir}" -type f ! -name '.gitkeep' -print0 \
106111
| while IFS= read -r -d '' f; do
107112
rel="${f#"${CUSTOM_BASE}"/}"
108-
mkdir -p "$(dirname "${rel}")"
109-
cp "${f}" "${rel}"
113+
mkdir -p "$(dirname "${DEST}${rel}")"
114+
cp "${f}" "${DEST}${rel}"
110115
done
111116
fi
112117
done
@@ -162,6 +167,7 @@ jobs:
162167
with:
163168
agent: retro
164169
version: ${{ inputs.fullsend_version }}
170+
fullsend-dir: ${{ inputs.install_mode == 'per-repo' && '.fullsend' || '' }}
165171
run-url: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
166172
status-repo: ${{ inputs.source_repo }}
167173
status-number: ${{ fromJSON(inputs.event_payload).pull_request.number || fromJSON(inputs.event_payload).issue.number }}

.github/workflows/reusable-review.yml

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,15 +86,20 @@ jobs:
8686
run: |
8787
set -euo pipefail
8888
if [[ "${INSTALL_MODE}" != "per-org" && "${INSTALL_MODE}" != "per-repo" ]]; then
89-
echo "::error::Invalid install_mode '${INSTALL_MODE}': must be 'per-org' or 'per-repo'"
89+
printf 'Received install_mode: %q\n' "${INSTALL_MODE}"
90+
echo "::error::Invalid install_mode: must be 'per-org' or 'per-repo'"
9091
exit 1
9192
fi
9293
SRC=".defaults/internal/scaffold/fullsend-repo"
9394
LAYERED_DIRS="agents skills schemas harness plugins policies scripts env"
95+
DEST=""
96+
if [[ "${INSTALL_MODE}" == "per-repo" ]]; then
97+
DEST=".fullsend/"
98+
fi
9499
for dir in ${LAYERED_DIRS}; do
95100
if [[ -d "${SRC}/${dir}" ]]; then
96-
mkdir -p "${dir}"
97-
cp -r "${SRC}/${dir}/." "${dir}/"
101+
mkdir -p "${DEST}${dir}"
102+
cp -r "${SRC}/${dir}/." "${DEST}${dir}/"
98103
fi
99104
done
100105
CUSTOM_BASE="customized"
@@ -106,8 +111,8 @@ jobs:
106111
find "${CUSTOM_BASE}/${dir}" -type f ! -name '.gitkeep' -print0 \
107112
| while IFS= read -r -d '' f; do
108113
rel="${f#"${CUSTOM_BASE}"/}"
109-
mkdir -p "$(dirname "${rel}")"
110-
cp "${f}" "${rel}"
114+
mkdir -p "$(dirname "${DEST}${rel}")"
115+
cp "${f}" "${DEST}${rel}"
111116
done
112117
fi
113118
done
@@ -176,6 +181,7 @@ jobs:
176181
PRIOR_REVIEW_PROVENANCE: ${{ steps.prior-review.outputs.prior_review_provenance }}
177182
with:
178183
agent: review
184+
fullsend-dir: ${{ inputs.install_mode == 'per-repo' && '.fullsend' || '' }}
179185
version: ${{ inputs.fullsend_version }}
180186
run-url: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
181187
status-repo: ${{ inputs.source_repo }}

.github/workflows/reusable-triage.yml

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,15 +86,20 @@ jobs:
8686
run: |
8787
set -euo pipefail
8888
if [[ "${INSTALL_MODE}" != "per-org" && "${INSTALL_MODE}" != "per-repo" ]]; then
89-
echo "::error::Invalid install_mode '${INSTALL_MODE}': must be 'per-org' or 'per-repo'"
89+
printf 'Received install_mode: %q\n' "${INSTALL_MODE}"
90+
echo "::error::Invalid install_mode: must be 'per-org' or 'per-repo'"
9091
exit 1
9192
fi
9293
SRC=".defaults/internal/scaffold/fullsend-repo"
9394
LAYERED_DIRS="agents skills schemas harness plugins policies scripts env"
95+
DEST=""
96+
if [[ "${INSTALL_MODE}" == "per-repo" ]]; then
97+
DEST=".fullsend/"
98+
fi
9499
for dir in ${LAYERED_DIRS}; do
95100
if [[ -d "${SRC}/${dir}" ]]; then
96-
mkdir -p "${dir}"
97-
cp -r "${SRC}/${dir}/." "${dir}/"
101+
mkdir -p "${DEST}${dir}"
102+
cp -r "${SRC}/${dir}/." "${DEST}${dir}/"
98103
fi
99104
done
100105
CUSTOM_BASE="customized"
@@ -106,8 +111,8 @@ jobs:
106111
find "${CUSTOM_BASE}/${dir}" -type f ! -name '.gitkeep' -print0 \
107112
| while IFS= read -r -d '' f; do
108113
rel="${f#"${CUSTOM_BASE}"/}"
109-
mkdir -p "$(dirname "${rel}")"
110-
cp "${f}" "${rel}"
114+
mkdir -p "$(dirname "${DEST}${rel}")"
115+
cp "${f}" "${DEST}${rel}"
111116
done
112117
fi
113118
done
@@ -161,6 +166,7 @@ jobs:
161166
with:
162167
agent: triage
163168
version: ${{ inputs.fullsend_version }}
169+
fullsend-dir: ${{ inputs.install_mode == 'per-repo' && '.fullsend' || '' }}
164170
run-url: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
165171
status-repo: ${{ inputs.source_repo }}
166172
status-number: ${{ fromJSON(inputs.event_payload).issue.number }}

docs/plans/deprecate-per-org-install.md

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -440,10 +440,11 @@ All per-org commands gone.
440440
into the renamed `Config` struct and `ParseConfig()`. This field is
441441
accessed by `run.go` (~lines 189, 192, 201, 205, 237, 243) and
442442
`lock.go` (~lines 170, 173, 181, 185, 264, 268, 270) via
443-
`tryLoadOrgConfig()`/`requireOrgConfig()` in `orgconfig.go`. Update
444-
`orgconfig.go` to use `config.ParseConfig()` instead of
445-
`config.ParseOrgConfig()` and rename functions/file accordingly
446-
(e.g. `tryLoadConfig()`/`requireConfig()`, rename file to
443+
`tryLoadFullsendConfig()`/`requireFullsendConfig()` (renamed from
444+
`tryLoadOrgConfig()`/`requireOrgConfig()`, which remain as var
445+
aliases) in `orgconfig.go`. Update `orgconfig.go` to use
446+
`config.ParseConfig()` instead of `config.ParseOrgConfig()` and
447+
rename functions/file accordingly (e.g. rename file to
447448
`configloader.go` or inline into callers).
448449

449450
**Update all callers:**
@@ -468,11 +469,12 @@ All per-org commands gone.
468469
`DefaultRoles()` for consistency. Effectively dead code after
469470
per-org removal.
470471
- `internal/cli/orgconfig.go`: Update `config.ParseOrgConfig()` calls
471-
(~lines 22, 42) to `config.ParseConfig()`. Rename
472-
`tryLoadOrgConfig()``tryLoadConfig()`, `requireOrgConfig()`
473-
`requireConfig()`. Rename file to `configloader.go` or inline.
474-
- `internal/cli/run.go`: Update `tryLoadOrgConfig()`/
475-
`requireOrgConfig()` calls (~lines 189, 198, 201, 235, 237) and
472+
to `config.ParseConfig()`. The functions were already renamed to
473+
`tryLoadFullsendConfig()`/`requireFullsendConfig()` (PR #3000); var
474+
aliases `tryLoadOrgConfig`/`requireOrgConfig` can be removed.
475+
Rename file to `configloader.go` or inline.
476+
- `internal/cli/run.go`: Update `tryLoadOrgConfig`/
477+
`requireOrgConfig` var alias calls (~lines 189, 198, 201, 235, 237) and
476478
`orgCfg.AllowedRemoteResources` accesses (~lines 192, 205, 243)
477479
to use the renamed config type and loader functions. Also update
478480
`config.ParseOrgConfig()` (~line 1974): this call reads
@@ -483,8 +485,8 @@ All per-org commands gone.
483485
and update `ParseConfig()` to populate it. This keeps status
484486
notification configuration available in per-repo mode without
485487
importing the full `RepoDefaults` sub-struct.
486-
- `internal/cli/lock.go`: Update `tryLoadOrgConfig()`/
487-
`requireOrgConfig()` calls (~lines 170, 181, 264) and
488+
- `internal/cli/lock.go`: Update `tryLoadOrgConfig`/
489+
`requireOrgConfig` var alias calls (~lines 170, 181, 264) and
488490
`orgCfg.AllowedRemoteResources` accesses (~lines 173, 185, 268,
489491
270) to use the renamed config type and loader functions.
490492
- Any other files importing `config.PerRepoConfig` or

internal/cli/lock_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1079,7 +1079,7 @@ func TestRunLock_URLRefsNoOrgConfigError(t *testing.T) {
10791079
printer := ui.New(os.Stdout)
10801080
err := runLock(context.Background(), "noconfig", dir, "", false, resolveFlags{}, printer)
10811081
require.Error(t, err)
1082-
assert.Contains(t, err.Error(), "URL-referenced resources require an org-level config.yaml")
1082+
assert.Contains(t, err.Error(), "URL-referenced resources require a config.yaml")
10831083
assert.Contains(t, err.Error(), "allowed_remote_resources")
10841084
}
10851085

@@ -1136,7 +1136,7 @@ func TestRunLock_MalformedOrgConfigWithURLRefs(t *testing.T) {
11361136
printer := ui.New(os.Stdout)
11371137
err := runLock(context.Background(), "badcfg", dir, "", false, resolveFlags{}, printer)
11381138
require.Error(t, err)
1139-
assert.Contains(t, err.Error(), "parsing org config")
1139+
assert.Contains(t, err.Error(), "parsing config")
11401140
}
11411141

11421142
func TestRunLock_NoOrgConfigNoURLRefs(t *testing.T) {
@@ -1202,7 +1202,7 @@ func TestRunLock_OrgAllowlistSyncedAfterReAttempt(t *testing.T) {
12021202
printer := ui.New(os.Stdout)
12031203
err := runLock(context.Background(), "urlrefs", dir, "", false, resolveFlags{}, printer)
12041204
require.Error(t, err)
1205-
assert.Contains(t, err.Error(), "parsing org config")
1205+
assert.Contains(t, err.Error(), "parsing config")
12061206
}
12071207

12081208
func TestRunLock_URLBaseAndURLRefsNoOrgConfig(t *testing.T) {

0 commit comments

Comments
 (0)